Bug 1440714 - Convert Android browser test harnesses to adb.py; r=bc

This affects Android robocop, mochitest (all flavors) and reftests (all flavors).
This commit is contained in:
Geoff Brown 2018-03-23 18:06:27 -06:00
Родитель 3341d77b2f
Коммит 6083932cff
9 изменённых файлов: 353 добавлений и 579 удалений

Просмотреть файл

@ -13,7 +13,6 @@ import shutil
import sys
from automation import Automation
from mozdevice import DMError, DeviceManager
from mozlog import get_default_logger
from mozscreenshot import dump_screen
import mozcrash
@ -24,31 +23,18 @@ fennecLogcatFilters = [ "The character encoding of the HTML document was not dec
"Unexpected value from nativeGetEnabledTags: 0" ]
class RemoteAutomation(Automation):
_devicemanager = None
def __init__(self, deviceManager, appName = '', remoteLog = None,
def __init__(self, device, appName = '', remoteProfile = None, remoteLog = None,
processArgs=None):
self._dm = deviceManager
self._device = device
self._appName = appName
self._remoteProfile = None
self._remoteProfile = remoteProfile
self._remoteLog = remoteLog
self._processArgs = processArgs or {};
self.lastTestSeen = "remoteautomation.py"
Automation.__init__(self)
def setDeviceManager(self, deviceManager):
self._dm = deviceManager
def setAppName(self, appName):
self._appName = appName
def setRemoteProfile(self, remoteProfile):
self._remoteProfile = remoteProfile
def setRemoteLog(self, logfile):
self._remoteLog = logfile
# Set up what we need for the remote environment
def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, lsanPath=None, ubsanPath=None):
# Because we are running remote, we don't want to mimic the local env
@ -56,12 +42,6 @@ class RemoteAutomation(Automation):
if env is None:
env = {}
# Except for the mochitest results table hiding option, which isn't
# passed to runtestsremote.py as an actual option, but through the
# MOZ_HIDE_RESULTS_TABLE environment variable.
if 'MOZ_HIDE_RESULTS_TABLE' in os.environ:
env['MOZ_HIDE_RESULTS_TABLE'] = os.environ['MOZ_HIDE_RESULTS_TABLE']
if crashreporter and not debugger:
env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
env['MOZ_CRASHREPORTER'] = '1'
@ -101,7 +81,7 @@ class RemoteAutomation(Automation):
status = proc.wait(timeout = maxTime, noOutputTimeout = timeout)
self.lastTestSeen = proc.getLastTestSeen
topActivity = self._dm.getTopActivity()
topActivity = self._device.get_top_activity(timeout=60)
if topActivity == proc.procName:
print "Browser unexpectedly found running. Killing..."
proc.kill(True)
@ -123,19 +103,16 @@ class RemoteAutomation(Automation):
# we make it empty and writable so we can test the ANR reporter later
traces = "/data/anr/traces.txt"
try:
self._dm.shellCheckOutput(['echo', '', '>', traces], root=True,
timeout=DeviceManager.short_timeout)
self._dm.shellCheckOutput(['chmod', '666', traces], root=True,
timeout=DeviceManager.short_timeout)
except DMError:
print "Error deleting %s" % traces
pass
self._device.shell_output('echo > %s' % traces, root=True)
self._device.shell_output('chmod 666 %s' % traces, root=True)
except Exception as e:
print "Error deleting %s: %s" % (traces, str(e))
def checkForANRs(self):
traces = "/data/anr/traces.txt"
if self._dm.fileExists(traces):
if self._device.is_file(traces):
try:
t = self._dm.pullFile(traces)
t = self._device.get_file(traces)
if t:
stripped = t.strip()
if len(stripped) > 0:
@ -143,22 +120,14 @@ class RemoteAutomation(Automation):
print t
# Once reported, delete traces
self.deleteANRs()
except DMError:
print "Error pulling %s" % traces
except IOError:
print "Error pulling %s" % traces
except Exception as e:
print "Error pulling %s: %s" % (traces, str(e))
else:
print "%s not found" % traces
def deleteTombstones(self):
# delete any tombstone files from device
tombstones = "/data/tombstones/*"
try:
self._dm.shellCheckOutput(['rm', '-r', tombstones], root=True,
timeout=DeviceManager.short_timeout)
except DMError:
# This may just indicate that the tombstone directory is missing
pass
self._device.rm("/data/tombstones", force=True, recursive=True, root=True)
def checkForTombstones(self):
# pull any tombstones from device and move to MOZ_UPLOAD_DIR
@ -167,17 +136,10 @@ class RemoteAutomation(Automation):
if uploadDir:
if not os.path.exists(uploadDir):
os.mkdir(uploadDir)
if self._dm.dirExists(remoteDir):
if self._device.is_dir(remoteDir):
# copy tombstone files from device to local upload directory
try:
self._dm.shellCheckOutput(['chmod', '777', remoteDir], root=True,
timeout=DeviceManager.short_timeout)
self._dm.shellCheckOutput(['chmod', '666', os.path.join(remoteDir, '*')],
root=True, timeout=DeviceManager.short_timeout)
self._dm.getDirectory(remoteDir, uploadDir, False)
except DMError:
# This may just indicate that no tombstone files are present
pass
self._device.chmod(remoteDir, recursive=True, root=True)
self._device.pull(remoteDir, uploadDir)
self.deleteTombstones()
for f in glob.glob(os.path.join(uploadDir, "tombstone_??")):
# add a unique integer to the file name, in case there are
@ -197,7 +159,7 @@ class RemoteAutomation(Automation):
self.checkForANRs()
self.checkForTombstones()
logcat = self._dm.getLogcat(filterOutRegexps=fennecLogcatFilters)
logcat = self._device.get_logcat(filter_out_regexps=fennecLogcatFilters)
javaException = mozcrash.check_for_java_exception(logcat, test_name=self.lastTestSeen)
if javaException:
@ -211,14 +173,14 @@ class RemoteAutomation(Automation):
try:
dumpDir = tempfile.mkdtemp()
remoteCrashDir = posixpath.join(self._remoteProfile, 'minidumps')
if not self._dm.dirExists(remoteCrashDir):
if not self._device.is_dir(remoteCrashDir):
# If crash reporting is enabled (MOZ_CRASHREPORTER=1), the
# minidumps directory is automatically created when Fennec
# (first) starts, so its lack of presence is a hint that
# something went wrong.
print "Automation Error: No crash directory (%s) found on remote device" % remoteCrashDir
return True
self._dm.getDirectory(remoteCrashDir, dumpDir)
self._device.pull(remoteCrashDir, dumpDir)
logger = get_default_logger()
crashed = mozcrash.log_crashes(logger, dumpDir, symbolsPath, test=self.lastTestSeen)
@ -226,8 +188,8 @@ class RemoteAutomation(Automation):
finally:
try:
shutil.rmtree(dumpDir)
except:
print "WARNING: unable to remove directory: %s" % dumpDir
except Exception as e:
print "WARNING: unable to remove directory %s: %s" % (dumpDir, str(e))
return crashed
def buildCommandLine(self, app, debuggerInfo, profileDir, testURL, extraArgs):
@ -235,7 +197,7 @@ class RemoteAutomation(Automation):
if self._remoteProfile:
profileDir = self._remoteProfile
# Hack for robocop, if app & testURL == None and extraArgs contains the rest of the stuff, lets
# Hack for robocop, if app is "am" and extraArgs contains the rest of the stuff, lets
# assume extraArgs is all we need
if app == "am" and extraArgs[0] in ('instrument', 'start'):
return app, extraArgs
@ -248,18 +210,18 @@ class RemoteAutomation(Automation):
return app, args
def Process(self, cmd, stdout = None, stderr = None, env = None, cwd = None):
return self.RProcess(self._dm, cmd, self._remoteLog, env, cwd, self._appName,
return self.RProcess(self._device, cmd, self._remoteLog, env, cwd, self._appName,
**self._processArgs)
class RProcess(object):
dm = None
def __init__(self, dm, cmd, stdout=None, env=None, cwd=None, app=None,
def __init__(self, device, cmd, stdout=None, env=None, cwd=None, app=None,
messageLogger=None, counts=None):
self.dm = dm
self.stdoutlen = 0
self.device = device
self.lastTestSeen = "remoteautomation.py"
self.proc = dm.launchProcess(cmd, stdout, cwd, env, True)
self.messageLogger = messageLogger
self.proc = stdout
self.procName = cmd[0].split(posixpath.sep)[-1]
self.stdoutlen = 0
self.utilityPath = None
self.counts = counts
@ -268,11 +230,25 @@ class RemoteAutomation(Automation):
self.counts['fail'] = 0
self.counts['todo'] = 0
if self.proc is None:
self.proc = stdout
self.procName = cmd[0].split(posixpath.sep)[-1]
if cmd[0] == 'am' and cmd[1] in ('instrument', 'start'):
if cmd[0] == 'am':
cmd = ' '.join(cmd)
self.procName = app
if not self.device.shell_bool(cmd):
print "remote_automation.py failed to launch %s" % cmd
else:
args = cmd
if args[0] == app:
args = args[1:]
url = args[-1:][0]
if url.startswith('/'):
# this is probably a reftest profile directory, not a url
url = None
else:
args = args[:-1]
if 'geckoview' in app:
self.device.launch_geckoview_example(app, moz_env=env, extra_args=args, url=url)
else:
self.device.launch_fennec(app, moz_env=env, extra_args=args, url=url)
# Setting timeout at 1 hour since on a remote device this takes much longer.
# Temporarily increased to 90 minutes because no more chunks can be created.
@ -283,25 +259,25 @@ class RemoteAutomation(Automation):
@property
def pid(self):
pid = self.dm.processExist(self.procName)
# HACK: we should probably be more sophisticated about monitoring
# running processes for the remote case, but for now we'll assume
# that this method can be called when nothing exists and it is not
# an error
if pid is None:
procs = self.device.get_process_list()
# limit the comparison to the first 75 characters due to a
# limitation in processname length in android.
pids = [proc[0] for proc in procs if proc[1] == self.procName[:75]]
if pids is None or len(pids) < 1:
return 0
return pid
return pids[0]
def read_stdout(self):
"""
Fetch the full remote log file using devicemanager, process them and
return whether there were any new log entries since the last call.
Fetch the full remote log file, log any new content and return True if new
content processed.
"""
if not self.dm.fileExists(self.proc):
if not self.device.is_file(self.proc):
return False
try:
newLogContent = self.dm.pullFile(self.proc, self.stdoutlen)
except DMError:
newLogContent = self.device.get_file(self.proc, offset=self.stdoutlen)
except Exception:
return False
if not newLogContent:
return False
@ -375,7 +351,7 @@ class RemoteAutomation(Automation):
top = self.procName
slowLog = False
endTime = datetime.datetime.now() + datetime.timedelta(seconds = timeout)
while (top == self.procName):
while top == self.procName:
# Get log updates on each interval, but if it is taking
# too long, only do it every 60 seconds
hasOutput = False
@ -398,10 +374,10 @@ class RemoteAutomation(Automation):
status = 2
break
if not hasOutput:
top = self.dm.getTopActivity()
if top == "":
top = self.device.get_top_activity(timeout=60)
if top is None:
print "Failed to get top activity, retrying, once..."
top = self.dm.getTopActivity()
top = self.device.get_top_activity(timeout=60)
# Flush anything added to stdout during the sleep
self.read_stdout()
return status
@ -416,23 +392,30 @@ class RemoteAutomation(Automation):
dump_screen(self.utilityPath, get_default_logger())
if stagedShutdown:
# Trigger an ANR report with "kill -3" (SIGQUIT)
self.dm.killProcess(self.procName, 3)
try:
self.device.pkill(self.procName, sig=3, attempts=1)
except:
pass
time.sleep(3)
# Trigger a breakpad dump with "kill -6" (SIGABRT)
self.dm.killProcess(self.procName, 6)
try:
self.device.pkill(self.procName, sig=6, attempts=1)
except:
pass
# Wait for process to end
retries = 0
while retries < 3:
pid = self.dm.processExist(self.procName)
if pid and pid > 0:
if self.device.process_exist(self.procName):
print "%s still alive after SIGABRT: waiting..." % self.procName
time.sleep(5)
else:
return
retries += 1
self.dm.killProcess(self.procName, 9)
pid = self.dm.processExist(self.procName)
if pid and pid > 0:
self.dm.killProcess(self.procName)
try:
self.device.pkill(self.procName, sig=9, attempts=1)
except:
print "%s still alive after SIGKILL!" % self.procName
if self.device.process_exist(self.procName):
self.device.pkill(self.procName)
else:
self.dm.killProcess(self.procName)
self.device.pkill(self.procName)

Просмотреть файл

@ -406,40 +406,19 @@ class RemoteArgumentsParser(ReftestArgumentsParser):
utilityPath="",
localLogName=None)
self.add_argument("--remote-app-path",
action="store",
type=str,
dest="remoteAppPath",
help="Path to remote executable relative to device root using only "
"forward slashes. Either this or app must be specified, "
"but not both.")
self.add_argument("--adbpath",
action="store",
type=str,
dest="adb_path",
default=None,
default="adb",
help="path to adb")
self.add_argument("--deviceIP",
action="store",
type=str,
dest="deviceIP",
help="ip address of remote device to test")
self.add_argument("--deviceSerial",
action="store",
type=str,
dest="deviceSerial",
help="adb serial number of remote device to test")
self.add_argument("--devicePort",
action="store",
type=str,
default="20701",
dest="devicePort",
help="port of remote device to test")
self.add_argument("--remote-webserver",
action="store",
type=str,
@ -458,21 +437,6 @@ class RemoteArgumentsParser(ReftestArgumentsParser):
dest="sslPort",
help="Port for https traffic to the web server")
self.add_argument("--remote-logfile",
action="store",
type=str,
dest="remoteLogFile",
default="reftest.log",
help="Name of log file on the device relative to device root. "
"PLEASE USE ONLY A FILENAME.")
self.add_argument("--pidfile",
action="store",
type=str,
dest="pidFile",
default="",
help="name of the pidfile to generate")
self.add_argument("--remoteTestRoot",
action="store",
type=str,
@ -493,16 +457,9 @@ class RemoteArgumentsParser(ReftestArgumentsParser):
help="do not display verbose diagnostics about the remote device")
def validate_remote(self, options, automation):
# Ensure our defaults are set properly for everything we can infer
if not options.remoteTestRoot:
options.remoteTestRoot = automation._dm.deviceRoot + \
'/reftest'
options.remoteProfile = options.remoteTestRoot + "/profile"
if options.remoteWebServer is None:
options.remoteWebServer = self.get_ip()
# Verify that our remotewebserver is set properly
if options.remoteWebServer == '127.0.0.1':
self.error("ERROR: Either you specified the loopback for the remote webserver or ",
"your local IP cannot be detected. "
@ -514,19 +471,6 @@ class RemoteArgumentsParser(ReftestArgumentsParser):
if not options.sslPort:
options.sslPort = automation.DEFAULT_SSL_PORT
# One of remoteAppPath (relative path to application) or the app (executable) must be
# set, but not both. If both are set, we destroy the user's selection for app
# so instead of silently destroying a user specificied setting, we
# error.
if options.remoteAppPath and options.app:
self.error(
"ERROR: You cannot specify both the remoteAppPath and the app")
elif options.remoteAppPath:
options.app = options.remoteTestRoot + "/" + options.remoteAppPath
elif options.app is None:
# Neither remoteAppPath nor app are set -- error
self.error("ERROR: You must specify either appPath or app")
if options.xrePath is None:
self.error(
"ERROR: You must specify the path to the controller xre directory")
@ -534,24 +478,6 @@ class RemoteArgumentsParser(ReftestArgumentsParser):
# Ensure xrepath is a full path
options.xrePath = os.path.abspath(options.xrePath)
options.localLogName = options.remoteLogFile
options.remoteLogFile = options.remoteTestRoot + \
'/' + options.remoteLogFile
# Ensure that the options.logfile (which the base class uses) is set to
# the remote setting when running remote. Also, if the user set the
# log file name there, use that instead of reusing the remotelogfile as
# above.
if options.logFile:
# If the user specified a local logfile name use that
options.localLogName = options.logFile
options.logFile = options.remoteLogFile
if options.pidFile != "":
with open(options.pidFile, 'w') as f:
f.write(str(os.getpid()))
# httpd-path is specified by standard makefile targets and may be specified
# on the command line to select a particular version of httpd.js. If not
# specified, try to select the one from hostutils.zip, as required in
@ -559,16 +485,6 @@ class RemoteArgumentsParser(ReftestArgumentsParser):
if not options.httpdPath:
options.httpdPath = os.path.join(options.utilityPath, "components")
if not options.ignoreWindowSize:
parts = automation._dm.getInfo(
'screen')['screen'][0].split()
width = int(parts[0].split(':')[1])
height = int(parts[1].split(':')[1])
if (width < 1366 or height < 1050):
self.error("ERROR: Invalid screen resolution %sx%s, "
"please adjust to 1366x1050 or higher" % (
width, height))
# Disable e10s by default on Android because we don't run Android
# e10s jobs anywhere yet.
options.e10s = False

Просмотреть файл

@ -2,8 +2,8 @@
# 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 logging
import os
import posixpath
import psutil
import signal
import sys
@ -13,7 +13,7 @@ import traceback
import urllib2
from contextlib import closing
import mozdevice
from mozdevice import ADBAndroid
import mozinfo
from automation import Automation
from remoteautomation import RemoteAutomation, fennecLogcatFilters
@ -54,14 +54,13 @@ class ReftestServer:
def __init__(self, automation, options, scriptDir):
self.automation = automation
self._utilityPath = options.utilityPath
self._xrePath = options.xrePath
self._profileDir = options.serverProfilePath
self.utilityPath = options.utilityPath
self.xrePath = options.xrePath
self.profileDir = options.serverProfilePath
self.webServer = options.remoteWebServer
self.httpPort = options.httpPort
self.scriptDir = scriptDir
self.pidFile = options.pidFile
self._httpdPath = os.path.abspath(options.httpdPath)
self.httpdPath = os.path.abspath(options.httpdPath)
if options.remoteWebServer == "10.0.2.2":
# probably running an Android emulator and 10.0.2.2 will
# not be visible from host
@ -74,20 +73,20 @@ class ReftestServer:
def start(self):
"Run the Refest server, returning the process ID of the server."
env = self.automation.environment(xrePath=self._xrePath)
env = self.automation.environment(xrePath=self.xrePath)
env["XPCOM_DEBUG_BREAK"] = "warn"
if self.automation.IS_WIN32:
env["PATH"] = env["PATH"] + ";" + self._xrePath
env["PATH"] = env["PATH"] + ";" + self.xrePath
args = ["-g", self._xrePath,
"-f", os.path.join(self._httpdPath, "httpd.js"),
args = ["-g", self.xrePath,
"-f", os.path.join(self.httpdPath, "httpd.js"),
"-e", "const _PROFILE_PATH = '%(profile)s';const _SERVER_PORT = "
"'%(port)s'; const _SERVER_ADDR ='%(server)s';" % {
"profile": self._profileDir.replace('\\', '\\\\'), "port": self.httpPort,
"profile": self.profileDir.replace('\\', '\\\\'), "port": self.httpPort,
"server": self.webServer},
"-f", os.path.join(self.scriptDir, "server.js")]
xpcshell = os.path.join(self._utilityPath,
xpcshell = os.path.join(self.utilityPath,
"xpcshell" + self.automation.BIN_SUFFIX)
if not os.access(xpcshell, os.F_OK):
@ -104,15 +103,10 @@ class ReftestServer:
return 2
self.automation.log.info("INFO | remotereftests.py | Server pid: %d", pid)
if (self.pidFile != ""):
f = open(self.pidFile + ".xpcshell.pid", 'w')
f.write("%s" % pid)
f.close()
def ensureReady(self, timeout):
assert timeout >= 0
aliveFile = os.path.join(self._profileDir, "server_alive.txt")
aliveFile = os.path.join(self.profileDir, "server_alive.txt")
i = 0
while i < timeout:
if os.path.exists(aliveFile):
@ -143,37 +137,74 @@ class ReftestServer:
class RemoteReftest(RefTest):
use_marionette = False
remoteApp = ''
resolver_cls = RemoteReftestResolver
def __init__(self, automation, devicemanager, options, scriptDir):
def __init__(self, options, scriptDir):
RefTest.__init__(self, options.suite)
self.run_by_manifest = False
self.automation = automation
self._devicemanager = devicemanager
self.scriptDir = scriptDir
self.remoteApp = options.app
self.localLogName = options.localLogName
verbose = False
if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
verbose = True
print "set verbose!"
self.device = ADBAndroid(adb=options.adb_path,
device=options.deviceSerial,
test_root=options.remoteTestRoot,
verbose=verbose)
if options.remoteTestRoot is None:
options.remoteTestRoot = posixpath.join(self.device.test_root, "reftest")
options.remoteProfile = posixpath.join(options.remoteTestRoot, "profile")
options.remoteLogFile = posixpath.join(options.remoteTestRoot, "reftest.log")
options.logFile = options.remoteLogFile
self.remoteProfile = options.remoteProfile
self.remoteTestRoot = options.remoteTestRoot
self.remoteLogFile = options.remoteLogFile
self.remoteCache = os.path.join(options.remoteTestRoot, "cache/")
self.localLogName = options.localLogName
self.pidFile = options.pidFile
if self.automation.IS_DEBUG_BUILD:
self.SERVER_STARTUP_TIMEOUT = 180
else:
self.SERVER_STARTUP_TIMEOUT = 90
self.automation.deleteANRs()
self.automation.deleteTombstones()
self._devicemanager.removeDir(self.remoteCache)
if not options.ignoreWindowSize:
parts = self.device.get_info(
'screen')['screen'][0].split()
width = int(parts[0].split(':')[1])
height = int(parts[1].split(':')[1])
if (width < 1366 or height < 1050):
self.error("ERROR: Invalid screen resolution %sx%s, "
"please adjust to 1366x1050 or higher" % (
width, height))
self._populate_logger(options)
self.outputHandler = OutputHandler(self.log, options.utilityPath, options.symbolsPath)
# RemoteAutomation.py's 'messageLogger' is also used by mochitest. Mimic a mochitest
# MessageLogger object to re-use this code path.
self.outputHandler.write = self.outputHandler.__call__
self.automation = RemoteAutomation(self.device, options.app, self.remoteProfile,
options.remoteLogFile, processArgs=None)
self.automation._processArgs['messageLogger'] = self.outputHandler
self.environment = self.automation.environment
if self.automation.IS_DEBUG_BUILD:
self.SERVER_STARTUP_TIMEOUT = 180
else:
self.SERVER_STARTUP_TIMEOUT = 90
self.remoteCache = os.path.join(options.remoteTestRoot, "cache/")
# Check that Firefox is installed
expected = options.app.split('/')[-1]
if not self.device.is_app_installed(expected):
raise Exception("%s is not installed on this device" % expected)
self.automation.deleteANRs()
self.automation.deleteTombstones()
self.device.clear_logcat()
self.device.rm(self.remoteCache, force=True, recursive=True)
procName = options.app.split('/')[-1]
self.device.pkill(procName)
if self.device.process_exist(procName):
self.log.error("unable to kill %s before starting tests!" % procName)
def findPath(self, paths, filename=None):
for path in paths:
p = path
@ -283,9 +314,9 @@ class RemoteReftest(RefTest):
profile.set_preferences(prefs)
try:
self._devicemanager.pushDir(profileDir, options.remoteProfile)
self._devicemanager.chmodDir(options.remoteProfile)
except mozdevice.DMError:
self.device.push(profileDir, options.remoteProfile)
self.device.chmod(options.remoteProfile, recursive=True)
except Exception:
print "Automation Error: Failed to copy profiledir to device"
raise
@ -294,20 +325,21 @@ class RemoteReftest(RefTest):
def copyExtraFilesToProfile(self, options, profile):
profileDir = profile.profile
RefTest.copyExtraFilesToProfile(self, options, profile)
try:
self._devicemanager.pushDir(profileDir, options.remoteProfile)
self._devicemanager.chmodDir(options.remoteProfile)
except mozdevice.DMError:
print "Automation Error: Failed to copy extra files to device"
raise
if len(os.listdir(profileDir)) > 0:
try:
self.device.push(profileDir, options.remoteProfile)
self.device.chmod(options.remoteProfile, recursive=True)
except Exception:
print "Automation Error: Failed to copy extra files to device"
raise
def printDeviceInfo(self, printLogcat=False):
try:
if printLogcat:
logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
logcat = self.device.get_logcat(filter_out_regexps=fennecLogcatFilters)
print ''.join(logcat)
print "Device info:"
devinfo = self._devicemanager.getInfo()
devinfo = self.device.get_info()
for category in devinfo:
if type(devinfo[category]) is list:
print " %s:" % category
@ -315,9 +347,9 @@ class RemoteReftest(RefTest):
print " %s" % item
else:
print " %s: %s" % (category, devinfo[category])
print "Test root: %s" % self._devicemanager.deviceRoot
except mozdevice.DMError:
print "WARNING: Error getting device information"
print "Test root: %s" % self.device.test_root
except Exception as e:
print "WARNING: Error getting device information: %s" % str(e)
def environment(self, **kwargs):
return self.automation.environment(**kwargs)
@ -358,64 +390,15 @@ class RemoteReftest(RefTest):
return status
def cleanup(self, profileDir):
# Pull results back from device
if self.remoteLogFile and \
self._devicemanager.fileExists(self.remoteLogFile):
self._devicemanager.getFile(self.remoteLogFile, self.localLogName)
else:
print "WARNING: Unable to retrieve log file (%s) from remote " \
"device" % self.remoteLogFile
self._devicemanager.removeDir(self.remoteProfile)
self._devicemanager.removeDir(self.remoteCache)
self._devicemanager.removeDir(self.remoteTestRoot)
self.device.rm(self.remoteTestRoot, force=True, recursive=True)
self.device.rm(self.remoteProfile, force=True, recursive=True)
self.device.rm(self.remoteCache, force=True, recursive=True)
RefTest.cleanup(self, profileDir)
if (self.pidFile != ""):
try:
os.remove(self.pidFile)
os.remove(self.pidFile + ".xpcshell.pid")
except Exception:
print ("Warning: cleaning up pidfile '%s' was unsuccessful "
"from the test harness" % self.pidFile)
def run_test_harness(parser, options):
dm_args = {
'deviceRoot': options.remoteTestRoot,
'host': options.deviceIP,
'port': options.devicePort,
}
dm_args['adbPath'] = options.adb_path
if not dm_args['host']:
dm_args['deviceSerial'] = options.deviceSerial
if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
dm_args['logLevel'] = logging.DEBUG
try:
dm = mozdevice.DroidADB(**dm_args)
except mozdevice.DMError:
traceback.print_exc()
print ("Automation Error: exception while initializing devicemanager. "
"Most likely the device is not in a testable state.")
return 1
automation = RemoteAutomation(None)
automation.setDeviceManager(dm)
# Set up the defaults and ensure options are set
parser.validate_remote(options, automation)
# Check that Firefox is installed
expected = options.app.split('/')[-1]
installed = dm.shellCheckOutput(['pm', 'list', 'packages', expected])
if expected not in installed:
print "%s is not installed on this device" % expected
return 1
automation.setAppName(options.app)
automation.setRemoteProfile(options.remoteProfile)
automation.setRemoteLog(options.remoteLogFile)
reftest = RemoteReftest(automation, dm, options, SCRIPT_DIRECTORY)
reftest = RemoteReftest(options, SCRIPT_DIRECTORY)
parser.validate_remote(options, reftest.automation)
parser.validate(options, reftest)
if mozinfo.info['debug']:
@ -438,20 +421,11 @@ def run_test_harness(parser, options):
if retVal:
return retVal
procName = options.app.split('/')[-1]
dm.killProcess(procName)
if dm.processExist(procName):
print "unable to kill %s before starting tests!" % procName
if options.printDeviceInfo:
reftest.printDeviceInfo()
# an example manifest name to use on the cli
# manifest = "http://" + options.remoteWebServer +
# "/reftests/layout/reftests/reftest-sanity/reftest.list"
retVal = 0
try:
dm.recordLogcat()
if options.verify:
retVal = reftest.verifyTests(options.tests, options)
else:

Просмотреть файл

@ -8,12 +8,10 @@ from distutils.util import strtobool
from distutils import spawn
from itertools import chain
from urlparse import urlparse
import logging
import json
import os
import tempfile
from mozdevice import DroidADB
from mozprofile import DEFAULT_PORTS
import mozinfo
import mozlog
@ -850,18 +848,6 @@ class AndroidArguments(ArgumentContainer):
"""Android specific arguments."""
args = [
[["--remote-app-path"],
{"dest": "remoteAppPath",
"help": "Path to remote executable relative to device root using \
only forward slashes. Either this or app must be specified \
but not both.",
"default": None,
}],
[["--deviceIP"],
{"dest": "deviceIP",
"help": "ip address of remote device to test",
"default": None,
}],
[["--deviceSerial"],
{"dest": "deviceSerial",
"help": "ip address of remote device to test",
@ -869,22 +855,10 @@ class AndroidArguments(ArgumentContainer):
}],
[["--adbpath"],
{"dest": "adbPath",
"default": None,
"default": "adb",
"help": "Path to adb binary.",
"suppress": True,
}],
[["--devicePort"],
{"dest": "devicePort",
"type": int,
"default": 20701,
"help": "port of remote device to test",
}],
[["--remote-logfile"],
{"dest": "remoteLogFile",
"default": None,
"help": "Name of log file on the device relative to the device \
root. PLEASE ONLY USE A FILENAME.",
}],
[["--remote-webserver"],
{"dest": "remoteWebServer",
"default": None,
@ -902,11 +876,6 @@ class AndroidArguments(ArgumentContainer):
"help": "ssl port of the remote web server",
"suppress": True,
}],
[["--robocop-ini"],
{"dest": "robocopIni",
"default": "",
"help": "name of the .ini file containing the list of tests to run",
}],
[["--robocop-apk"],
{"dest": "robocopApk",
"default": "",
@ -922,7 +891,6 @@ class AndroidArguments(ArgumentContainer):
]
defaults = {
'dm': None,
# we don't want to exclude specialpowers on android just yet
'extensionsToExclude': [],
# mochijar doesn't get installed via marionette on android
@ -937,21 +905,6 @@ class AndroidArguments(ArgumentContainer):
if build_obj:
options.log_mach = '-'
device_args = {'deviceRoot': options.remoteTestRoot}
device_args['adbPath'] = options.adbPath
if options.deviceIP:
device_args['host'] = options.deviceIP
device_args['port'] = options.devicePort
elif options.deviceSerial:
device_args['deviceSerial'] = options.deviceSerial
if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
device_args['logLevel'] = logging.DEBUG
options.dm = DroidADB(**device_args)
if not options.remoteTestRoot:
options.remoteTestRoot = options.dm.deviceRoot
if options.remoteWebServer is None:
if os.name != "nt":
options.remoteWebServer = moznetwork.get_ip()
@ -961,24 +914,10 @@ class AndroidArguments(ArgumentContainer):
options.webServer = options.remoteWebServer
if options.remoteLogFile is None:
options.remoteLogFile = options.remoteTestRoot + \
'/logs/mochitest.log'
if options.remoteLogFile.count('/') < 1:
options.remoteLogFile = options.remoteTestRoot + \
'/' + options.remoteLogFile
if options.remoteAppPath and options.app:
parser.error(
"You cannot specify both the remoteAppPath and the app setting")
elif options.remoteAppPath:
options.app = options.remoteTestRoot + "/" + options.remoteAppPath
elif options.app is None:
if options.app is None:
if build_obj:
options.app = build_obj.substs['ANDROID_PACKAGE_NAME']
else:
# Neither remoteAppPath nor app are set -- error
parser.error("You must specify either appPath or app")
if build_obj and 'MOZ_HOST_BIN' in os.environ:
@ -996,16 +935,8 @@ class AndroidArguments(ArgumentContainer):
f.write("%s" % os.getpid())
f.close()
# Robocop specific options
if options.robocopIni != "":
if not os.path.exists(options.robocopIni):
parser.error(
"Unable to find specified robocop .ini manifest '%s'" %
options.robocopIni)
options.robocopIni = os.path.abspath(options.robocopIni)
if not options.robocopApk and build_obj:
options.robocopApk = build_obj.substs.get('GRADLE_ANDROID_APP_ANDROIDTEST_APK')
if not options.robocopApk and build_obj:
options.robocopApk = build_obj.substs.get('GRADLE_ANDROID_APP_ANDROIDTEST_APK')
if options.robocopApk != "":
if not os.path.exists(options.robocopApk):

Просмотреть файл

@ -225,7 +225,7 @@ def run_test_harness(log, parser, options):
runner.cleanup()
except Exception:
# ignore device error while cleaning up
pass
traceback.print_exc()
return result

Просмотреть файл

@ -4,6 +4,7 @@
import json
import os
import posixpath
import sys
import tempfile
import traceback
@ -21,7 +22,8 @@ from mochitest_options import MochitestArgumentParser
from manifestparser import TestManifest
from manifestparser.filters import chunk_by_slice
import mozdevice
from mozdevice import ADBAndroid
import mozfile
import mozinfo
SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
@ -33,40 +35,50 @@ class RobocopTestRunner(MochitestDesktop):
based on the Robotium test framework. This harness leverages some functionality
from mochitest, for convenience.
"""
auto = None
dm = None
# Some robocop tests run for >60 seconds without generating any output.
NO_OUTPUT_TIMEOUT = 180
def __init__(self, automation, devmgr, options):
def __init__(self, options, message_logger):
"""
Simple one-time initialization.
"""
MochitestDesktop.__init__(self, options.flavor, vars(options))
self.auto = automation
self.dm = devmgr
self.dm.default_timeout = 320
verbose = False
if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
verbose = True
self.device = ADBAndroid(adb=options.adbPath,
device=options.deviceSerial,
test_root=options.remoteTestRoot,
verbose=verbose)
# Check that Firefox is installed
expected = options.app.split('/')[-1]
if not self.device.is_app_installed(expected):
raise Exception("%s is not installed on this device" % expected)
options.logFile = "robocop.log"
if options.remoteTestRoot is None:
options.remoteTestRoot = self.device.test_root
self.remoteProfile = posixpath.join(options.remoteTestRoot, "profile")
self.remoteProfileCopy = posixpath.join(options.remoteTestRoot, "profile-copy")
self.remoteConfigFile = posixpath.join(options.remoteTestRoot, "robotium.config")
self.remoteLogFile = posixpath.join(options.remoteTestRoot, "logs", "robocop.log")
self.options = options
self.options.logFile = "robocop.log"
process_args = {'messageLogger': message_logger}
self.auto = RemoteAutomation(self.device, options.remoteappname, self.remoteProfile,
self.remoteLogFile, processArgs=process_args)
self.environment = self.auto.environment
self.deviceRoot = self.dm.getDeviceRoot()
self.remoteProfile = options.remoteTestRoot + "/profile"
self.remoteProfileCopy = options.remoteTestRoot + "/profile-copy"
self.auto.setRemoteProfile(self.remoteProfile)
self.remoteConfigFile = os.path.join(
self.deviceRoot, "robotium.config")
self.remoteLog = options.remoteLogFile
self.auto.setRemoteLog(self.remoteLog)
self.remoteScreenshots = "/mnt/sdcard/Robotium-Screenshots"
self.remoteMozLog = os.path.join(options.remoteTestRoot, "mozlog")
self.auto.setServerInfo(
self.options.webServer, self.options.httpPort, self.options.sslPort)
self.remoteMozLog = posixpath.join(options.remoteTestRoot, "mozlog")
self.localLog = options.logFile
self.localProfile = None
self.auto.setAppName(self.options.remoteappname)
self.certdbNew = True
self.remoteCopyAvailable = True
self.passed = 0
self.failed = 0
self.todo = 0
@ -85,23 +97,23 @@ class RobocopTestRunner(MochitestDesktop):
self.auto.deleteANRs()
self.auto.deleteTombstones()
procName = self.options.app.split('/')[-1]
self.dm.killProcess(procName)
if self.dm.processExist(procName):
self.device.pkill(procName)
if self.device.process_exist(procName):
self.log.warning("unable to kill %s before running tests!" % procName)
self.dm.removeDir(self.remoteScreenshots)
self.dm.removeDir(self.remoteMozLog)
self.dm.mkDir(self.remoteMozLog)
self.dm.mkDir(os.path.dirname(self.options.remoteLogFile))
self.device.rm(self.remoteScreenshots, force=True, recursive=True)
self.device.rm(self.remoteMozLog, force=True, recursive=True)
self.device.mkdir(self.remoteMozLog)
logParent = posixpath.dirname(self.remoteLogFile)
self.device.rm(logParent, force=True, recursive=True)
self.device.mkdir(logParent)
# Add Android version (SDK level) to mozinfo so that manifest entries
# can be conditional on android_version.
androidVersion = self.dm.shellCheckOutput(
['getprop', 'ro.build.version.sdk'])
self.log.info(
"Android sdk version '%s'; will use this to filter manifests" %
str(androidVersion))
mozinfo.info['android_version'] = androidVersion
str(self.device.version))
mozinfo.info['android_version'] = str(self.device.version)
if self.options.robocopApk:
self.dm._checkCmd(["install", "-r", self.options.robocopApk])
self.device.install_app(self.options.robocopApk, replace=True)
self.log.debug("Robocop APK %s installed" %
self.options.robocopApk)
# Display remote diagnostics; if running in mach, keep output terse.
@ -122,23 +134,22 @@ class RobocopTestRunner(MochitestDesktop):
"""
self.log.debug("Cleaning up...")
self.stopServers()
self.dm.killProcess(self.options.app.split('/')[-1])
blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
if blobberUploadDir:
self.device.pkill(self.options.app.split('/')[-1])
uploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
if uploadDir:
self.log.debug("Pulling any remote moz logs and screenshots to %s." %
blobberUploadDir)
self.dm.getDirectory(self.remoteMozLog, blobberUploadDir)
self.dm.getDirectory(self.remoteScreenshots, blobberUploadDir)
uploadDir)
self.device.pull(self.remoteMozLog, uploadDir)
self.device.pull(self.remoteScreenshots, uploadDir)
MochitestDesktop.cleanup(self, self.options)
if self.localProfile:
os.system("rm -Rf %s" % self.localProfile)
self.dm.removeDir(self.remoteProfile)
self.dm.removeDir(self.remoteProfileCopy)
self.dm.removeDir(self.remoteScreenshots)
self.dm.removeDir(self.remoteMozLog)
self.dm.removeFile(self.remoteConfigFile)
if self.dm.fileExists(self.remoteLog):
self.dm.removeFile(self.remoteLog)
mozfile.remove(self.localProfile)
self.device.rm(self.remoteProfile, force=True, recursive=True)
self.device.rm(self.remoteProfileCopy, force=True, recursive=True)
self.device.rm(self.remoteScreenshots, force=True, recursive=True)
self.device.rm(self.remoteMozLog, force=True, recursive=True)
self.device.rm(self.remoteConfigFile, force=True)
self.device.rm(self.remoteLogFile, force=True)
self.log.debug("Cleanup complete.")
def findPath(self, paths, filename=None):
@ -236,8 +247,8 @@ class RobocopTestRunner(MochitestDesktop):
# some files are not needed for robocop; save time by not pushing
os.remove(os.path.join(self.localProfile, 'userChrome.css'))
try:
self.dm.pushDir(self.localProfile, self.remoteProfileCopy)
except mozdevice.DMError:
self.device.push(self.localProfile, self.remoteProfileCopy)
except Exception:
self.log.error(
"Automation Error: Unable to copy profile to device.")
raise
@ -249,20 +260,8 @@ class RobocopTestRunner(MochitestDesktop):
Remove any remote profile and re-create it.
"""
self.log.debug("Updating remote profile at %s" % self.remoteProfile)
self.dm.removeDir(self.remoteProfile)
if self.remoteCopyAvailable:
try:
self.dm.shellCheckOutput(
['cp', '-r', self.remoteProfileCopy, self.remoteProfile],
root=True, timeout=60)
except mozdevice.DMError:
# For instance, cp is not available on some older versions of
# Android.
self.log.info(
"Unable to copy remote profile; falling back to push.")
self.remoteCopyAvailable = False
if not self.remoteCopyAvailable:
self.dm.pushDir(self.localProfile, self.remoteProfile)
self.device.rm(self.remoteProfile, force=True, recursive=True)
self.device.cp(self.remoteProfileCopy, self.remoteProfile, recursive=True)
def parseLocalLog(self):
"""
@ -328,7 +327,7 @@ class RobocopTestRunner(MochitestDesktop):
"""
try:
if printLogcat:
logcat = self.dm.getLogcat(
logcat = self.device.get_logcat(
filterOutRegexps=fennecLogcatFilters)
self.log.info(
'\n' +
@ -336,7 +335,7 @@ class RobocopTestRunner(MochitestDesktop):
'utf-8',
'replace'))
self.log.info("Device info:")
devinfo = self.dm.getInfo()
devinfo = self.device.get_info()
for category in devinfo:
if type(devinfo[category]) is list:
self.log.info(" %s:" % category)
@ -344,9 +343,9 @@ class RobocopTestRunner(MochitestDesktop):
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")
self.log.info("Test root: %s" % self.device.test_root)
except Exception as e:
self.log.warning("Error getting device information: %s" % str(e))
def setupRobotiumConfig(self, browserEnv):
"""
@ -356,8 +355,8 @@ class RobocopTestRunner(MochitestDesktop):
prefix='robotium-',
dir=os.getcwd(),
delete=False)
fHandle.write("profile=%s\n" % (self.remoteProfile))
fHandle.write("logfile=%s\n" % (self.options.remoteLogFile))
fHandle.write("profile=%s\n" % self.remoteProfile)
fHandle.write("logfile=%s\n" % self.remoteLogFile)
fHandle.write("host=http://mochi.test:8888/tests\n")
fHandle.write(
"rawhost=http://%s:%s/tests\n" %
@ -377,8 +376,8 @@ class RobocopTestRunner(MochitestDesktop):
delim = ","
fHandle.write("envvars=%s\n" % envstr)
fHandle.close()
self.dm.removeFile(self.remoteConfigFile)
self.dm.pushFile(fHandle.name, self.remoteConfigFile)
self.device.rm(self.remoteConfigFile, force=True)
self.device.push(fHandle.name, self.remoteConfigFile)
os.unlink(fHandle.name)
def buildBrowserEnv(self):
@ -419,15 +418,15 @@ class RobocopTestRunner(MochitestDesktop):
self.setupRobotiumConfig(browserEnv)
self.setupRemoteProfile()
self.options.app = "am"
timeout = None
if self.options.autorun:
# This launches a test (using "am instrument") and instructs
# Fennec to /quit/ the browser (using Robocop:Quit) and to
# /finish/ all opened activities.
browserArgs = [
"instrument",
"-w",
"-e", "quit_and_finish", "1",
"-e", "deviceroot", self.deviceRoot,
"-e", "deviceroot", self.device.test_root,
"-e", "class",
"org.mozilla.gecko.tests.%s" % test['name'].split('/')[-1].split('.java')[0],
"org.mozilla.roboexample.test/org.mozilla.gecko.FennecInstrumentationTestRunner"]
@ -438,7 +437,8 @@ class RobocopTestRunner(MochitestDesktop):
browserArgs = ["start", "-n",
"org.mozilla.roboexample.test/org.mozilla."
"gecko.LaunchFennecWithConfigurationActivity", "&&", "cat"]
self.dm.default_timeout = sys.maxint # Forever.
timeout = sys.maxint # Forever.
self.log.info("")
self.log.info("Serving mochi.test Robocop root at http://%s:%s/tests/robocop/" %
(self.options.remoteWebServer, self.options.httpPort))
@ -446,8 +446,9 @@ class RobocopTestRunner(MochitestDesktop):
result = -1
log_result = -1
try:
self.dm.recordLogcat()
timeout = self.options.timeout
self.device.clear_logcat()
if not timeout:
timeout = self.options.timeout
if not timeout:
timeout = self.NO_OUTPUT_TIMEOUT
result, _ = self.auto.runApp(
@ -456,15 +457,9 @@ class RobocopTestRunner(MochitestDesktop):
self.log.debug("runApp completes with status %d" % result)
if result != 0:
self.log.error("runApp() exited with code %s" % result)
if self.dm.fileExists(self.remoteLog):
self.dm.getFile(self.remoteLog, self.localLog)
self.dm.removeFile(self.remoteLog)
self.log.debug("Remote log %s retrieved to %s" %
(self.remoteLog, self.localLog))
else:
self.log.warning(
"Unable to retrieve log file (%s) from remote device" %
self.remoteLog)
if self.device.is_file(self.remoteLogFile):
self.device.pull(self.remoteLogFile, self.localLog)
self.device.rm(self.remoteLogFile)
log_result = self.parseLocalLog()
if result != 0 or log_result != 0:
# Display remote diagnostics; if running in mach, keep output
@ -486,7 +481,7 @@ class RobocopTestRunner(MochitestDesktop):
mp = self.options.manifestFile
else:
mp = TestManifest(strict=False)
mp.read(self.options.robocopIni)
mp.read("robocop.ini")
filters = []
if self.options.totalChunks:
filters.append(
@ -537,18 +532,8 @@ def run_test_harness(parser, options):
raise ValueError(
"Invalid options specified, use --help for a list of valid options")
message_logger = MessageLogger(logger=None)
process_args = {'messageLogger': message_logger}
auto = RemoteAutomation(None, "fennec", processArgs=process_args)
auto.setDeviceManager(options.dm)
runResult = -1
robocop = RobocopTestRunner(auto, options.dm, options)
# Check that Firefox is installed
expected = options.app.split('/')[-1]
installed = options.dm.shellCheckOutput(['pm', 'list', 'packages', expected])
if expected not in installed:
robocop.log.error("%s is not installed on this device" % expected)
return 1
robocop = RobocopTestRunner(options, message_logger)
try:
message_logger.logger = robocop.log
@ -567,9 +552,9 @@ def run_test_harness(parser, options):
finally:
try:
robocop.cleanup()
except mozdevice.DMError:
except Exception:
# ignore device error while cleaning up
pass
traceback.print_exc()
message_logger.finish()
return runResult

Просмотреть файл

@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import posixpath
import sys
import traceback
@ -16,67 +17,102 @@ from remoteautomation import RemoteAutomation, fennecLogcatFilters
from runtests import MochitestDesktop, MessageLogger
from mochitest_options import MochitestArgumentParser
import mozdevice
from mozdevice import ADBAndroid
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):
def __init__(self, options):
MochitestDesktop.__init__(self, options.flavor, vars(options))
verbose = False
if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
verbose = True
if hasattr(options, 'log'):
delattr(options, 'log')
self._automation = automation
self._dm = devmgr
self.chromePushed = False
self.environment = self._automation.environment
self.remoteProfile = os.path.join(options.remoteTestRoot, "profile/")
self.remoteModulesDir = os.path.join(options.remoteTestRoot, "modules/")
self.remoteCache = os.path.join(options.remoteTestRoot, "cache/")
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)
self._dm.removeDir(self.remoteProfile)
self._dm.removeDir(self.remoteCache)
self.chromePushed = False
self.mozLogName = "moz.log"
self.device = ADBAndroid(adb=options.adbPath,
device=options.deviceSerial,
test_root=options.remoteTestRoot,
verbose=verbose)
if options.remoteTestRoot is None:
options.remoteTestRoot = self.device.test_root
options.dumpOutputDirectory = options.remoteTestRoot
self.remoteLogFile = posixpath.join(options.remoteTestRoot, "logs", "mochitest.log")
logParent = posixpath.dirname(self.remoteLogFile)
self.device.rm(logParent, force=True, recursive=True)
self.device.mkdir(logParent)
self.remoteProfile = posixpath.join(options.remoteTestRoot, "profile/")
self.device.rm(self.remoteProfile, force=True, recursive=True)
self.counts = dict()
self.message_logger = MessageLogger(logger=None)
self.message_logger.logger = self.log
process_args = {'messageLogger': self.message_logger, 'counts': self.counts}
self.automation = RemoteAutomation(self.device, options.remoteappname, self.remoteProfile,
self.remoteLogFile, processArgs=process_args)
self.environment = self.automation.environment
# Check that Firefox is installed
expected = options.app.split('/')[-1]
if not self.device.is_app_installed(expected):
raise Exception("%s is not installed on this device" % expected)
self.automation.deleteANRs()
self.automation.deleteTombstones()
self.device.clear_logcat()
self.remoteModulesDir = posixpath.join(options.remoteTestRoot, "modules/")
self.remoteCache = posixpath.join(options.remoteTestRoot, "cache/")
self.device.rm(self.remoteCache, force=True, recursive=True)
# move necko cache to a location that can be cleaned up
options.extraPrefs += ["browser.cache.disk.parent_directory=%s" % self.remoteCache]
self.remoteMozLog = posixpath.join(options.remoteTestRoot, "mozlog")
self.device.rm(self.remoteMozLog, force=True, recursive=True)
self.device.mkdir(self.remoteMozLog)
self.remoteChromeTestDir = posixpath.join(
options.remoteTestRoot,
"chrome")
self.device.rm(self.remoteChromeTestDir, force=True, recursive=True)
self.device.mkdir(self.remoteChromeTestDir)
procName = options.app.split('/')[-1]
self.device.pkill(procName)
if self.device.process_exist(procName):
self.log.warning("unable to kill %s before running tests!" % procName)
# Add Android version (SDK level) to mozinfo so that manifest entries
# can be conditional on android_version.
self.log.info(
"Android sdk version '%s'; will use this to filter manifests" %
str(self.device.version))
mozinfo.info['android_version'] = str(self.device.version)
def cleanup(self, options, final=False):
if final:
self._dm.removeDir(self.remoteChromeTestDir)
self.device.rm(self.remoteChromeTestDir, force=True, recursive=True)
self.chromePushed = False
blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
if blobberUploadDir:
self._dm.getDirectory(self.remoteMozLog, blobberUploadDir)
else:
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.remoteCache)
uploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
if uploadDir:
self.device.pull(self.remoteMozLog, uploadDir)
self.device.rm(self.remoteLogFile, force=True)
self.device.rm(self.remoteProfile, force=True, recursive=True)
self.device.rm(self.remoteCache, force=True, recursive=True)
MochitestDesktop.cleanup(self, options, final)
self.localProfile = None
@ -179,9 +215,9 @@ class MochiRemote(MochitestDesktop):
restoreRemotePaths = self.switchToLocalPaths(options)
if options.testingModulesDir:
try:
self._dm.pushDir(options.testingModulesDir, self.remoteModulesDir)
self._dm.chmodDir(self.remoteModulesDir)
except mozdevice.DMError:
self.device.push(options.testingModulesDir, self.remoteModulesDir)
self.device.chmod(self.remoteModulesDir, recursive=True)
except Exception:
self.log.error(
"Automation Error: Unable to copy test modules to device.")
raise
@ -199,24 +235,22 @@ class MochiRemote(MochitestDesktop):
return manifest
def buildURLOptions(self, options, env):
self.localLog = options.logFile
options.logFile = self.remoteLog
options.fileLevel = 'INFO'
saveLogFile = options.logFile
options.logFile = self.remoteLogFile
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.")
self.device.push(options.profilePath, self.remoteProfile)
self.device.chmod(self.remoteProfile, recursive=True)
except Exception:
self.log.error("Automation Error: Unable to copy profile to device.")
raise
options.profilePath = self.remoteProfile
options.logFile = self.localLog
options.logFile = saveLogFile
return retVal
def getChromeTestDir(self, options):
@ -225,7 +259,7 @@ class MochiRemote(MochitestDesktop):
if options.flavor == 'chrome' and not self.chromePushed:
self.log.info("pushing %s to %s on device..." % (local, remote))
local = os.path.join(local, "chrome")
self._dm.pushDir(local, remote)
self.device.push(local, remote)
self.chromePushed = True
return remote
@ -235,7 +269,7 @@ class MochiRemote(MochitestDesktop):
def printDeviceInfo(self, printLogcat=False):
try:
if printLogcat:
logcat = self._dm.getLogcat(
logcat = self.device.get_logcat(
filterOutRegexps=fennecLogcatFilters)
self.log.info(
'\n' +
@ -243,7 +277,7 @@ class MochiRemote(MochitestDesktop):
'utf-8',
'replace'))
self.log.info("Device info:")
devinfo = self._dm.getInfo()
devinfo = self.device.get_info()
for category in devinfo:
if type(devinfo[category]) is list:
self.log.info(" %s:" % category)
@ -251,9 +285,9 @@ class MochiRemote(MochitestDesktop):
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")
self.log.info("Test root: %s" % self.device.test_root)
except Exception as e:
self.log.warning("Error getting device information: %s" % str(e))
def getGMPPluginPath(self, options):
# TODO: bug 1149374
@ -287,7 +321,7 @@ class MochiRemote(MochitestDesktop):
# remove args not supported by automation.py
kwargs.pop('marionette_args', None)
ret, _ = self._automation.runApp(*args, **kwargs)
ret, _ = self.automation.runApp(*args, **kwargs)
self.countpass += self.counts['pass']
self.countfail += self.counts['fail']
self.counttodo += self.counts['todo']
@ -298,11 +332,6 @@ class MochiRemote(MochitestDesktop):
def run_test_harness(parser, options):
parser.validate(options)
message_logger = MessageLogger(logger=None)
counts = dict()
process_args = {'messageLogger': message_logger, 'counts': counts}
auto = RemoteAutomation(None, options.app, processArgs=process_args)
if options is None:
raise ValueError("Invalid options specified, use --help for a list of valid options")
@ -312,72 +341,30 @@ def run_test_harness(parser, options):
if options.flavor != 'chrome':
options.extensionsToExclude.append('roboextender@mozilla.org')
dm = options.dm
auto.setDeviceManager(dm)
mochitest = MochiRemote(auto, dm, options)
options.dm = None
log = mochitest.log
message_logger.logger = log
mochitest.message_logger = message_logger
mochitest.counts = counts
# 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
auto.setAppName(options.remoteappname)
logParent = os.path.dirname(options.remoteLogFile)
dm.removeDir(logParent)
dm.mkDir(logParent)
auto.setRemoteLog(options.remoteLogFile)
auto.setServerInfo(options.webServer, options.httpPort, options.sslPort)
mochitest = MochiRemote(options)
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
options.dumpOutputDirectory = deviceRoot
procName = options.app.split('/')[-1]
dm.killProcess(procName)
if dm.processExist(procName):
log.warning("unable to kill %s before running tests!" % procName)
mochitest.mozLogName = "moz.log"
try:
dm.recordLogcat()
if options.verify:
retVal = mochitest.verifyTests(options)
else:
retVal = mochitest.runTests(options)
except Exception:
log.error("Automation Error: Exception caught while running tests")
mochitest.log.error("Automation Error: Exception caught while running tests")
traceback.print_exc()
mochitest.stopServers()
try:
mochitest.cleanup(options)
except mozdevice.DMError:
except Exception:
# device error cleaning up... oh well!
pass
traceback.print_exc()
retVal = 1
if options.log_mach is None:
mochitest.printDeviceInfo(printLogcat=True)
message_logger.finish()
mochitest.message_logger.finish()
return retVal

Просмотреть файл

@ -148,7 +148,6 @@ config = {
"--log-raw=%(raw_log_file)s",
"--log-errorsummary=%(error_summary_file)s",
"--robocop-apk=../../robocop.apk",
"--robocop-ini=robocop.ini",
],
},
"reftest": {

Просмотреть файл

@ -248,8 +248,7 @@ class XPCShellRemote(xpcshell.XPCShellTests, object):
self.remoteTestRoot = posixpath.join(self.device.test_root, "xpc")
# Add Android version (SDK level) to mozinfo so that manifest entries
# can be conditional on android_version.
androidVersion = self.device.get_prop('ro.build.version.sdk')
mozinfo.info['android_version'] = androidVersion
mozinfo.info['android_version'] = self.device.version
self.localLib = options['localLib']
self.localBin = options['localBin']