123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
- import os
- import sys
- import traceback
- sys.path.insert(
- 0, os.path.abspath(
- os.path.realpath(
- os.path.dirname(__file__))))
- from automation import Automation
- from remoteautomation import RemoteAutomation, fennecLogcatFilters
- from runtests import MochitestDesktop, MessageLogger
- from mochitest_options import MochitestArgumentParser
- import mozdevice
- import mozinfo
- SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
- class MochiRemote(MochitestDesktop):
- _automation = None
- _dm = None
- localProfile = None
- logMessages = []
- def __init__(self, automation, devmgr, options):
- MochitestDesktop.__init__(self, options)
- self._automation = automation
- self._dm = devmgr
- self.environment = self._automation.environment
- self.remoteProfile = os.path.join(options.remoteTestRoot, "profile/")
- self.remoteModulesDir = os.path.join(options.remoteTestRoot, "modules/")
- self._automation.setRemoteProfile(self.remoteProfile)
- self.remoteLog = options.remoteLogFile
- self.localLog = options.logFile
- self._automation.deleteANRs()
- self._automation.deleteTombstones()
- self.certdbNew = True
- self.remoteMozLog = os.path.join(options.remoteTestRoot, "mozlog")
- self._dm.removeDir(self.remoteMozLog)
- self._dm.mkDir(self.remoteMozLog)
- self.remoteChromeTestDir = os.path.join(
- options.remoteTestRoot,
- "chrome")
- self._dm.removeDir(self.remoteChromeTestDir)
- self._dm.mkDir(self.remoteChromeTestDir)
- def cleanup(self, options):
- if self._dm.fileExists(self.remoteLog):
- self._dm.getFile(self.remoteLog, self.localLog)
- self._dm.removeFile(self.remoteLog)
- else:
- self.log.warning(
- "Unable to retrieve log file (%s) from remote device" %
- self.remoteLog)
- self._dm.removeDir(self.remoteProfile)
- self._dm.removeDir(self.remoteChromeTestDir)
- blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
- if blobberUploadDir:
- self._dm.getDirectory(self.remoteMozLog, blobberUploadDir)
- MochitestDesktop.cleanup(self, options)
- def findPath(self, paths, filename=None):
- for path in paths:
- p = path
- if filename:
- p = os.path.join(p, filename)
- if os.path.exists(self.getFullPath(p)):
- return path
- return None
- def makeLocalAutomation(self):
- localAutomation = Automation()
- localAutomation.IS_WIN32 = False
- localAutomation.IS_LINUX = False
- localAutomation.IS_MAC = False
- localAutomation.UNIXISH = False
- hostos = sys.platform
- if (hostos == 'mac' or hostos == 'darwin'):
- localAutomation.IS_MAC = True
- elif (hostos == 'linux' or hostos == 'linux2'):
- localAutomation.IS_LINUX = True
- localAutomation.UNIXISH = True
- elif (hostos == 'win32' or hostos == 'win64'):
- localAutomation.BIN_SUFFIX = ".exe"
- localAutomation.IS_WIN32 = True
- return localAutomation
- # This seems kludgy, but this class uses paths from the remote host in the
- # options, except when calling up to the base class, which doesn't
- # understand the distinction. This switches out the remote values for local
- # ones that the base class understands. This is necessary for the web
- # server, SSL tunnel and profile building functions.
- def switchToLocalPaths(self, options):
- """ Set local paths in the options, return a function that will restore remote values """
- remoteXrePath = options.xrePath
- remoteProfilePath = options.profilePath
- remoteUtilityPath = options.utilityPath
- localAutomation = self.makeLocalAutomation()
- paths = [
- options.xrePath,
- localAutomation.DIST_BIN,
- self._automation._product,
- os.path.join('..', self._automation._product)
- ]
- options.xrePath = self.findPath(paths)
- if options.xrePath is None:
- self.log.error(
- "unable to find xulrunner path for %s, please specify with --xre-path" %
- os.name)
- sys.exit(1)
- xpcshell = "xpcshell"
- if (os.name == "nt"):
- xpcshell += ".exe"
- if options.utilityPath:
- paths = [options.utilityPath, options.xrePath]
- else:
- paths = [options.xrePath]
- options.utilityPath = self.findPath(paths, xpcshell)
- if options.utilityPath is None:
- self.log.error(
- "unable to find utility path for %s, please specify with --utility-path" %
- os.name)
- sys.exit(1)
- xpcshell_path = os.path.join(options.utilityPath, xpcshell)
- if localAutomation.elf_arm(xpcshell_path):
- self.log.error('xpcshell at %s is an ARM binary; please use '
- 'the --utility-path argument to specify the path '
- 'to a desktop version.' % xpcshell_path)
- sys.exit(1)
- if self.localProfile:
- options.profilePath = self.localProfile
- else:
- options.profilePath = None
- def fixup():
- options.xrePath = remoteXrePath
- options.utilityPath = remoteUtilityPath
- options.profilePath = remoteProfilePath
- return fixup
- def startServers(self, options, debuggerInfo):
- """ Create the servers on the host and start them up """
- restoreRemotePaths = self.switchToLocalPaths(options)
- # ignoreSSLTunnelExts is a workaround for bug 1109310
- MochitestDesktop.startServers(
- self,
- options,
- debuggerInfo,
- ignoreSSLTunnelExts=True)
- restoreRemotePaths()
- def buildProfile(self, options):
- restoreRemotePaths = self.switchToLocalPaths(options)
- if options.testingModulesDir:
- try:
- self._dm.pushDir(options.testingModulesDir, self.remoteModulesDir)
- self._dm.chmodDir(self.remoteModulesDir)
- except mozdevice.DMError:
- self.log.error(
- "Automation Error: Unable to copy test modules to device.")
- raise
- savedTestingModulesDir = options.testingModulesDir
- options.testingModulesDir = self.remoteModulesDir
- else:
- savedTestingModulesDir = None
- manifest = MochitestDesktop.buildProfile(self, options)
- if savedTestingModulesDir:
- options.testingModulesDir = savedTestingModulesDir
- self.localProfile = options.profilePath
- restoreRemotePaths()
- options.profilePath = self.remoteProfile
- return manifest
- def addChromeToProfile(self, options):
- manifest = MochitestDesktop.addChromeToProfile(self, options)
- # Support Firefox (browser), SeaMonkey (navigator), and Webapp Runtime (webapp).
- if options.flavor == 'chrome':
- # append overlay to chrome.manifest
- chrome = ("overlay chrome://browser/content/browser.xul "
- "chrome://mochikit/content/browser-test-overlay.xul")
- path = os.path.join(options.profilePath, 'extensions', 'staged',
- 'mochikit@mozilla.org', 'chrome.manifest')
- with open(path, "a") as f:
- f.write(chrome)
- return manifest
- def buildURLOptions(self, options, env):
- self.localLog = options.logFile
- options.logFile = self.remoteLog
- options.fileLevel = 'INFO'
- options.profilePath = self.localProfile
- env["MOZ_HIDE_RESULTS_TABLE"] = "1"
- retVal = MochitestDesktop.buildURLOptions(self, options, env)
- # we really need testConfig.js (for browser chrome)
- try:
- self._dm.pushDir(options.profilePath, self.remoteProfile)
- self._dm.chmodDir(self.remoteProfile)
- except mozdevice.DMError:
- self.log.error(
- "Automation Error: Unable to copy profile to device.")
- raise
- options.profilePath = self.remoteProfile
- options.logFile = self.localLog
- return retVal
- def getChromeTestDir(self, options):
- local = super(MochiRemote, self).getChromeTestDir(options)
- local = os.path.join(local, "chrome")
- remote = self.remoteChromeTestDir
- if options.flavor == 'chrome':
- self.log.info("pushing %s to %s on device..." % (local, remote))
- self._dm.pushDir(local, remote)
- return remote
- def getLogFilePath(self, logFile):
- return logFile
- def printDeviceInfo(self, printLogcat=False):
- try:
- if printLogcat:
- logcat = self._dm.getLogcat(
- filterOutRegexps=fennecLogcatFilters)
- self.log.info(
- '\n' +
- ''.join(logcat).decode(
- 'utf-8',
- 'replace'))
- self.log.info("Device info:")
- devinfo = self._dm.getInfo()
- for category in devinfo:
- if type(devinfo[category]) is list:
- self.log.info(" %s:" % category)
- for item in devinfo[category]:
- self.log.info(" %s" % item)
- else:
- self.log.info(" %s: %s" % (category, devinfo[category]))
- self.log.info("Test root: %s" % self._dm.deviceRoot)
- except mozdevice.DMError:
- self.log.warning("Error getting device information")
- def getGMPPluginPath(self, options):
- # TODO: bug 1149374
- return None
- def buildBrowserEnv(self, options, debugger=False):
- browserEnv = MochitestDesktop.buildBrowserEnv(
- self,
- options,
- debugger=debugger)
- # remove desktop environment not used on device
- if "MOZ_WIN_INHERIT_STD_HANDLES_PRE_VISTA" in browserEnv:
- del browserEnv["MOZ_WIN_INHERIT_STD_HANDLES_PRE_VISTA"]
- if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
- del browserEnv["XPCOM_MEM_BLOAT_LOG"]
- # override mozLogs to avoid processing in MochitestDesktop base class
- self.mozLogs = None
- browserEnv["MOZ_LOG_FILE"] = os.path.join(
- self.remoteMozLog,
- self.mozLogName)
- return browserEnv
- def runApp(self, *args, **kwargs):
- """front-end automation.py's `runApp` functionality until FennecRunner is written"""
- # automation.py/remoteautomation `runApp` takes the profile path,
- # whereas runtest.py's `runApp` takes a mozprofile object.
- if 'profileDir' not in kwargs and 'profile' in kwargs:
- kwargs['profileDir'] = kwargs.pop('profile').profile
- # remove args not supported by automation.py
- kwargs.pop('marionette_args', None)
- return self._automation.runApp(*args, **kwargs)
- def run_test_harness(parser, options):
- parser.validate(options)
- message_logger = MessageLogger(logger=None)
- process_args = {'messageLogger': message_logger}
- auto = RemoteAutomation(None, "fennec", processArgs=process_args)
- if options is None:
- raise ValueError("Invalid options specified, use --help for a list of valid options")
- options.runByDir = False
- # roboextender is used by mochitest-chrome tests like test_java_addons.html,
- # but not by any plain mochitests
- if options.flavor != 'chrome':
- options.extensionsToExclude.append('roboextender@mozilla.org')
- dm = options.dm
- auto.setDeviceManager(dm)
- mochitest = MochiRemote(auto, dm, options)
- log = mochitest.log
- message_logger.logger = log
- mochitest.message_logger = message_logger
- # Check that Firefox is installed
- expected = options.app.split('/')[-1]
- installed = dm.shellCheckOutput(['pm', 'list', 'packages', expected])
- if expected not in installed:
- log.error("%s is not installed on this device" % expected)
- return 1
- productPieces = options.remoteProductName.split('.')
- if (productPieces is not None):
- auto.setProduct(productPieces[0])
- else:
- auto.setProduct(options.remoteProductName)
- auto.setAppName(options.remoteappname)
- logParent = os.path.dirname(options.remoteLogFile)
- dm.mkDir(logParent)
- auto.setRemoteLog(options.remoteLogFile)
- auto.setServerInfo(options.webServer, options.httpPort, options.sslPort)
- if options.log_mach is None:
- mochitest.printDeviceInfo()
- # Add Android version (SDK level) to mozinfo so that manifest entries
- # can be conditional on android_version.
- androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
- log.info(
- "Android sdk version '%s'; will use this to filter manifests" %
- str(androidVersion))
- mozinfo.info['android_version'] = androidVersion
- deviceRoot = dm.deviceRoot
- if options.dmdPath:
- dmdLibrary = "libdmd.so"
- dmdPathOnDevice = os.path.join(deviceRoot, dmdLibrary)
- dm.removeFile(dmdPathOnDevice)
- dm.pushFile(os.path.join(options.dmdPath, dmdLibrary), dmdPathOnDevice)
- options.dmdPath = deviceRoot
- options.dumpOutputDirectory = deviceRoot
- procName = options.app.split('/')[-1]
- dm.killProcess(procName)
- mochitest.mozLogName = "moz.log"
- try:
- dm.recordLogcat()
- retVal = mochitest.runTests(options)
- except:
- log.error("Automation Error: Exception caught while running tests")
- traceback.print_exc()
- mochitest.stopServers()
- try:
- mochitest.cleanup(options)
- except mozdevice.DMError:
- # device error cleaning up... oh well!
- pass
- retVal = 1
- if options.log_mach is None:
- mochitest.printDeviceInfo(printLogcat=True)
- message_logger.finish()
- return retVal
- def main(args=sys.argv[1:]):
- parser = MochitestArgumentParser(app='android')
- options = parser.parse_args(args)
- return run_test_harness(parser, options)
- if __name__ == "__main__":
- sys.exit(main())
|