gecko-dev/testing/mochitest/runtestsremote.py

371 lines
13 KiB
Python

# 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 base64
import json
import os
import shutil
import sys
import tempfile
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 Mochitest, MessageLogger
from mochitest_options import MochitestArgumentParser
from manifestparser import TestManifest
from manifestparser.filters import chunk_by_slice
import devicemanager
import mozinfo
SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
class MochiRemote(Mochitest):
_automation = None
_dm = None
localProfile = None
logMessages = []
def __init__(self, automation, devmgr, options):
Mochitest.__init__(self, options)
self._automation = automation
self._dm = devmgr
self.environment = self._automation.environment
self.remoteProfile = options.remoteTestRoot + "/profile"
self._automation.setRemoteProfile(self.remoteProfile)
self.remoteLog = options.remoteLogFile
self.localLog = options.logFile
self._automation.deleteANRs()
self._automation.deleteTombstones()
self.certdbNew = True
self.remoteNSPR = os.path.join(options.remoteTestRoot, "nspr")
self._dm.removeDir(self.remoteNSPR)
self._dm.mkDir(self.remoteNSPR)
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.remoteNSPR, blobberUploadDir)
Mochitest.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
Mochitest.startServers(
self,
options,
debuggerInfo,
ignoreSSLTunnelExts=True)
restoreRemotePaths()
def buildProfile(self, options):
restoreRemotePaths = self.switchToLocalPaths(options)
manifest = Mochitest.buildProfile(self, options)
self.localProfile = options.profilePath
self._dm.removeDir(self.remoteProfile)
try:
self._dm.pushDir(options.profilePath, self.remoteProfile)
except devicemanager.DMError:
self.log.error(
"Automation Error: Unable to copy profile to device.")
raise
restoreRemotePaths()
options.profilePath = self.remoteProfile
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 = Mochitest.buildURLOptions(self, options, env)
# we really need testConfig.js (for browser chrome)
try:
self._dm.pushDir(options.profilePath, self.remoteProfile)
except devicemanager.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.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 devicemanager.DMError:
self.log.warning("Error getting device information")
def getGMPPluginPath(self, options):
# TODO: bug 1149374
return None
def buildBrowserEnv(self, options, debugger=False):
browserEnv = Mochitest.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 nsprLogs to avoid processing in Mochitest base class
self.nsprLogs = None
browserEnv["NSPR_LOG_FILE"] = os.path.join(
self.remoteNSPR,
self.nsprLogName)
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
if 'quiet' in kwargs:
kwargs.pop('quiet')
return self._automation.runApp(*args, **kwargs)
def run_test_harness(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
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.nsprLogName = "nspr.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 devicemanager.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(options)
if __name__ == "__main__":
sys.exit(main())