2012-05-21 11:12:37 +00:00
|
|
|
# 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/.
|
2010-03-24 17:51:17 +00:00
|
|
|
|
2010-02-25 19:10:39 +00:00
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import time
|
2010-05-27 20:02:15 +00:00
|
|
|
import tempfile
|
2012-02-09 13:49:00 +00:00
|
|
|
import re
|
2012-11-05 13:03:55 +00:00
|
|
|
import traceback
|
2012-12-28 12:18:22 +00:00
|
|
|
import shutil
|
2013-02-21 14:03:02 +00:00
|
|
|
import math
|
2013-03-26 17:31:23 +00:00
|
|
|
import base64
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
sys.path.insert(0, os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0]))))
|
|
|
|
|
|
|
|
from automation import Automation
|
2012-11-12 21:57:13 +00:00
|
|
|
from remoteautomation import RemoteAutomation, fennecLogcatFilters
|
2010-02-25 19:10:39 +00:00
|
|
|
from runtests import Mochitest
|
|
|
|
from runtests import MochitestOptions
|
2010-05-27 20:02:15 +00:00
|
|
|
from runtests import MochitestServer
|
2010-02-25 19:10:39 +00:00
|
|
|
|
2011-05-06 22:17:55 +00:00
|
|
|
import devicemanager, devicemanagerADB, devicemanagerSUT
|
2011-12-31 15:03:36 +00:00
|
|
|
import manifestparser
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
class RemoteOptions(MochitestOptions):
|
|
|
|
|
|
|
|
def __init__(self, automation, scriptdir, **kwargs):
|
|
|
|
defaults = {}
|
|
|
|
MochitestOptions.__init__(self, automation, scriptdir)
|
|
|
|
|
2010-06-24 09:32:01 +00:00
|
|
|
self.add_option("--remote-app-path", action="store",
|
|
|
|
type = "string", 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")
|
|
|
|
defaults["remoteAppPath"] = None
|
|
|
|
|
2010-02-25 19:10:39 +00:00
|
|
|
self.add_option("--deviceIP", action="store",
|
|
|
|
type = "string", dest = "deviceIP",
|
|
|
|
help = "ip address of remote device to test")
|
|
|
|
defaults["deviceIP"] = None
|
|
|
|
|
2011-05-06 22:17:55 +00:00
|
|
|
self.add_option("--dm_trans", action="store",
|
|
|
|
type = "string", dest = "dm_trans",
|
|
|
|
help = "the transport to use to communicate with device: [adb|sut]; default=sut")
|
|
|
|
defaults["dm_trans"] = "sut"
|
|
|
|
|
2010-02-25 19:10:39 +00:00
|
|
|
self.add_option("--devicePort", action="store",
|
|
|
|
type = "string", dest = "devicePort",
|
|
|
|
help = "port of remote device to test")
|
2010-05-27 20:02:15 +00:00
|
|
|
defaults["devicePort"] = 20701
|
2010-02-25 19:10:39 +00:00
|
|
|
|
2010-06-24 09:32:01 +00:00
|
|
|
self.add_option("--remote-product-name", action="store",
|
2010-02-25 19:10:39 +00:00
|
|
|
type = "string", dest = "remoteProductName",
|
2010-05-27 20:02:15 +00:00
|
|
|
help = "The executable's name of remote product to test - either fennec or firefox, defaults to fennec")
|
|
|
|
defaults["remoteProductName"] = "fennec"
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
self.add_option("--remote-logfile", action="store",
|
|
|
|
type = "string", dest = "remoteLogFile",
|
2010-05-27 20:02:15 +00:00
|
|
|
help = "Name of log file on the device relative to the device root. PLEASE ONLY USE A FILENAME.")
|
2010-02-25 19:10:39 +00:00
|
|
|
defaults["remoteLogFile"] = None
|
|
|
|
|
2010-03-13 18:34:19 +00:00
|
|
|
self.add_option("--remote-webserver", action = "store",
|
|
|
|
type = "string", dest = "remoteWebServer",
|
|
|
|
help = "ip address where the remote web server is hosted at")
|
|
|
|
defaults["remoteWebServer"] = None
|
|
|
|
|
|
|
|
self.add_option("--http-port", action = "store",
|
|
|
|
type = "string", dest = "httpPort",
|
2012-06-05 21:07:14 +00:00
|
|
|
help = "http port of the remote web server")
|
2010-03-13 18:34:19 +00:00
|
|
|
defaults["httpPort"] = automation.DEFAULT_HTTP_PORT
|
|
|
|
|
|
|
|
self.add_option("--ssl-port", action = "store",
|
|
|
|
type = "string", dest = "sslPort",
|
2012-06-05 21:07:14 +00:00
|
|
|
help = "ssl port of the remote web server")
|
2010-03-13 18:34:19 +00:00
|
|
|
defaults["sslPort"] = automation.DEFAULT_SSL_PORT
|
2010-02-25 19:10:39 +00:00
|
|
|
|
2011-04-19 22:17:01 +00:00
|
|
|
self.add_option("--pidfile", action = "store",
|
|
|
|
type = "string", dest = "pidFile",
|
|
|
|
help = "name of the pidfile to generate")
|
|
|
|
defaults["pidFile"] = ""
|
|
|
|
|
2011-12-31 15:03:36 +00:00
|
|
|
self.add_option("--robocop", action = "store",
|
|
|
|
type = "string", dest = "robocop",
|
2012-01-07 23:41:08 +00:00
|
|
|
help = "name of the .ini file containing the list of tests to run")
|
2011-12-31 15:03:36 +00:00
|
|
|
defaults["robocop"] = ""
|
|
|
|
|
2012-01-07 23:41:08 +00:00
|
|
|
self.add_option("--robocop-path", action = "store",
|
|
|
|
type = "string", dest = "robocopPath",
|
|
|
|
help = "Path to the folder where robocop.apk is located at. Primarily used for ADB test running")
|
|
|
|
defaults["robocopPath"] = ""
|
|
|
|
|
2012-01-30 18:14:47 +00:00
|
|
|
self.add_option("--robocop-ids", action = "store",
|
|
|
|
type = "string", dest = "robocopIds",
|
|
|
|
help = "name of the file containing the view ID map (fennec_ids.txt)")
|
|
|
|
defaults["robocopIds"] = ""
|
|
|
|
|
2012-12-20 16:11:11 +00:00
|
|
|
self.add_option("--remoteTestRoot", action = "store",
|
|
|
|
type = "string", dest = "remoteTestRoot",
|
|
|
|
help = "remote directory to use as test root (eg. /mnt/sdcard/tests or /data/local/tests)")
|
2010-05-27 20:02:15 +00:00
|
|
|
defaults["remoteTestRoot"] = None
|
2012-12-20 16:11:11 +00:00
|
|
|
|
2010-02-25 19:10:39 +00:00
|
|
|
defaults["logFile"] = "mochitest.log"
|
|
|
|
defaults["autorun"] = True
|
|
|
|
defaults["closeWhenDone"] = True
|
|
|
|
defaults["testPath"] = ""
|
2010-05-27 20:02:15 +00:00
|
|
|
defaults["app"] = None
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
self.set_defaults(**defaults)
|
|
|
|
|
2010-05-27 20:02:15 +00:00
|
|
|
def verifyRemoteOptions(self, options, automation):
|
2012-12-20 16:11:11 +00:00
|
|
|
if not options.remoteTestRoot:
|
|
|
|
options.remoteTestRoot = automation._devicemanager.getDeviceRoot()
|
2010-12-09 22:47:21 +00:00
|
|
|
productRoot = options.remoteTestRoot + "/" + automation._product
|
2010-05-27 20:02:15 +00:00
|
|
|
|
2010-12-09 22:47:21 +00:00
|
|
|
if (options.utilityPath == self._automation.DIST_BIN):
|
|
|
|
options.utilityPath = productRoot + "/bin"
|
2010-03-13 18:34:19 +00:00
|
|
|
|
2010-12-09 22:47:21 +00:00
|
|
|
if options.remoteWebServer == None:
|
|
|
|
if os.name != "nt":
|
|
|
|
options.remoteWebServer = automation.getLanIp()
|
|
|
|
else:
|
|
|
|
print "ERROR: you must specify a --remote-webserver=<ip address>\n"
|
|
|
|
return None
|
2010-05-27 20:02:15 +00:00
|
|
|
|
|
|
|
options.webServer = options.remoteWebServer
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
if (options.deviceIP == None):
|
2010-06-24 09:32:01 +00:00
|
|
|
print "ERROR: you must provide a device IP"
|
|
|
|
return None
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
if (options.remoteLogFile == None):
|
2011-07-07 17:10:52 +00:00
|
|
|
options.remoteLogFile = options.remoteTestRoot + '/logs/mochitest.log'
|
2010-09-29 23:20:33 +00:00
|
|
|
|
|
|
|
if (options.remoteLogFile.count('/') < 1):
|
2010-12-09 22:47:21 +00:00
|
|
|
options.remoteLogFile = options.remoteTestRoot + '/' + options.remoteLogFile
|
2010-06-15 20:02:17 +00:00
|
|
|
|
2010-06-24 09:32:01 +00:00
|
|
|
# remoteAppPath or app must be specified to find the product to launch
|
|
|
|
if (options.remoteAppPath and options.app):
|
|
|
|
print "ERROR: You cannot specify both the remoteAppPath and the app setting"
|
|
|
|
return None
|
|
|
|
elif (options.remoteAppPath):
|
|
|
|
options.app = options.remoteTestRoot + "/" + options.remoteAppPath
|
|
|
|
elif (options.app == None):
|
|
|
|
# Neither remoteAppPath nor app are set -- error
|
|
|
|
print "ERROR: You must specify either appPath or app"
|
|
|
|
return None
|
2010-05-27 20:02:15 +00:00
|
|
|
|
|
|
|
# Only reset the xrePath if it wasn't provided
|
|
|
|
if (options.xrePath == None):
|
2010-06-24 09:32:01 +00:00
|
|
|
if (automation._product == "fennec"):
|
|
|
|
options.xrePath = productRoot + "/xulrunner"
|
|
|
|
else:
|
|
|
|
options.xrePath = options.utilityPath
|
2010-05-27 20:02:15 +00:00
|
|
|
|
2011-04-19 22:17:01 +00:00
|
|
|
if (options.pidFile != ""):
|
|
|
|
f = open(options.pidFile, 'w')
|
|
|
|
f.write("%s" % os.getpid())
|
|
|
|
f.close()
|
|
|
|
|
2012-01-07 23:41:08 +00:00
|
|
|
# Robocop specific options
|
|
|
|
if options.robocop != "":
|
|
|
|
if not os.path.exists(options.robocop):
|
|
|
|
print "ERROR: Unable to find specified manifest '%s'" % options.robocop
|
|
|
|
return None
|
|
|
|
options.robocop = os.path.abspath(options.robocop)
|
|
|
|
|
|
|
|
if options.robocopPath != "":
|
|
|
|
if not os.path.exists(os.path.join(options.robocopPath, 'robocop.apk')):
|
|
|
|
print "ERROR: Unable to find robocop.apk in path '%s'" % options.robocopPath
|
|
|
|
return None
|
|
|
|
options.robocopPath = os.path.abspath(options.robocopPath)
|
|
|
|
|
2012-01-30 18:14:47 +00:00
|
|
|
if options.robocopIds != "":
|
|
|
|
if not os.path.exists(options.robocopIds):
|
|
|
|
print "ERROR: Unable to find specified IDs file '%s'" % options.robocopIds
|
|
|
|
return None
|
|
|
|
options.robocopIds = os.path.abspath(options.robocopIds)
|
|
|
|
|
2012-12-28 12:18:22 +00:00
|
|
|
# allow us to keep original application around for cleanup while running robocop via 'am'
|
|
|
|
options.remoteappname = options.app
|
2010-05-27 20:02:15 +00:00
|
|
|
return options
|
|
|
|
|
|
|
|
def verifyOptions(self, options, mochitest):
|
|
|
|
# since we are reusing verifyOptions, it will exit if App is not found
|
|
|
|
temp = options.app
|
|
|
|
options.app = sys.argv[0]
|
|
|
|
tempPort = options.httpPort
|
|
|
|
tempSSL = options.sslPort
|
|
|
|
tempIP = options.webServer
|
|
|
|
options = MochitestOptions.verifyOptions(self, options, mochitest)
|
|
|
|
options.webServer = tempIP
|
|
|
|
options.app = temp
|
|
|
|
options.sslPort = tempSSL
|
|
|
|
options.httpPort = tempPort
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
return options
|
|
|
|
|
|
|
|
class MochiRemote(Mochitest):
|
|
|
|
|
|
|
|
_automation = None
|
|
|
|
_dm = None
|
2012-01-07 23:41:08 +00:00
|
|
|
localProfile = None
|
2012-02-09 13:49:00 +00:00
|
|
|
logLines = []
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
def __init__(self, automation, devmgr, options):
|
|
|
|
self._automation = automation
|
|
|
|
Mochitest.__init__(self, self._automation)
|
|
|
|
self._dm = devmgr
|
|
|
|
self.runSSLTunnel = False
|
2010-05-27 20:02:15 +00:00
|
|
|
self.remoteProfile = options.remoteTestRoot + "/profile"
|
2011-10-06 14:51:03 +00:00
|
|
|
self._automation.setRemoteProfile(self.remoteProfile)
|
2010-02-25 19:10:39 +00:00
|
|
|
self.remoteLog = options.remoteLogFile
|
2012-10-03 15:07:31 +00:00
|
|
|
self.localLog = options.logFile
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
def cleanup(self, manifest, options):
|
2012-10-15 14:15:19 +00:00
|
|
|
if self._dm.fileExists(self.remoteLog):
|
|
|
|
self._dm.getFile(self.remoteLog, self.localLog)
|
|
|
|
self._dm.removeFile(self.remoteLog)
|
|
|
|
else:
|
|
|
|
print "WARNING: Unable to retrieve log file (%s) from remote " \
|
|
|
|
"device" % self.remoteLog
|
2010-02-25 19:10:39 +00:00
|
|
|
self._dm.removeDir(self.remoteProfile)
|
2012-01-11 13:50:10 +00:00
|
|
|
|
2011-04-19 22:17:01 +00:00
|
|
|
if (options.pidFile != ""):
|
|
|
|
try:
|
|
|
|
os.remove(options.pidFile)
|
2011-05-12 16:47:38 +00:00
|
|
|
os.remove(options.pidFile + ".xpcshell.pid")
|
2011-04-19 22:17:01 +00:00
|
|
|
except:
|
|
|
|
print "Warning: cleaning up pidfile '%s' was unsuccessful from the test harness" % options.pidFile
|
2010-02-25 19:10:39 +00:00
|
|
|
|
2010-05-27 20:02:15 +00:00
|
|
|
def findPath(self, paths, filename = None):
|
2010-06-24 09:32:01 +00:00
|
|
|
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
|
2010-05-27 20:02:15 +00:00
|
|
|
|
2010-02-25 19:10:39 +00:00
|
|
|
def startWebServer(self, options):
|
2010-06-24 09:32:01 +00:00
|
|
|
""" Create the webserver on the host and start it up """
|
|
|
|
remoteXrePath = options.xrePath
|
|
|
|
remoteProfilePath = options.profilePath
|
|
|
|
remoteUtilityPath = options.utilityPath
|
|
|
|
localAutomation = Automation()
|
2011-02-24 19:45:42 +00:00
|
|
|
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
|
2010-06-24 09:32:01 +00:00
|
|
|
|
|
|
|
paths = [options.xrePath, localAutomation.DIST_BIN, self._automation._product, os.path.join('..', self._automation._product)]
|
|
|
|
options.xrePath = self.findPath(paths)
|
|
|
|
if options.xrePath == None:
|
|
|
|
print "ERROR: unable to find xulrunner path for %s, please specify with --xre-path" % (os.name)
|
|
|
|
sys.exit(1)
|
|
|
|
paths.append("bin")
|
|
|
|
paths.append(os.path.join("..", "bin"))
|
|
|
|
|
|
|
|
xpcshell = "xpcshell"
|
|
|
|
if (os.name == "nt"):
|
|
|
|
xpcshell += ".exe"
|
2010-06-24 09:32:01 +00:00
|
|
|
|
2010-06-24 09:32:01 +00:00
|
|
|
if (options.utilityPath):
|
|
|
|
paths.insert(0, options.utilityPath)
|
|
|
|
options.utilityPath = self.findPath(paths, xpcshell)
|
|
|
|
if options.utilityPath == None:
|
|
|
|
print "ERROR: unable to find utility path for %s, please specify with --utility-path" % (os.name)
|
|
|
|
sys.exit(1)
|
|
|
|
|
2013-01-25 19:35:15 +00:00
|
|
|
xpcshell_path = os.path.join(options.utilityPath, xpcshell)
|
|
|
|
if localAutomation.elf_arm(xpcshell_path):
|
|
|
|
self.error('xpcshell at %s is an ARM binary; please use '
|
|
|
|
'the --utility-path argument to specify the path '
|
|
|
|
'to a desktop version.' % xpcshell)
|
|
|
|
|
2010-06-24 09:32:01 +00:00
|
|
|
options.profilePath = tempfile.mkdtemp()
|
|
|
|
self.server = MochitestServer(localAutomation, options)
|
|
|
|
self.server.start()
|
|
|
|
|
2011-05-12 16:47:38 +00:00
|
|
|
if (options.pidFile != ""):
|
|
|
|
f = open(options.pidFile + ".xpcshell.pid", 'w')
|
|
|
|
f.write("%s" % self.server._process.pid)
|
|
|
|
f.close()
|
2010-06-24 09:32:01 +00:00
|
|
|
self.server.ensureReady(self.SERVER_STARTUP_TIMEOUT)
|
2011-05-12 16:47:38 +00:00
|
|
|
|
2010-06-24 09:32:01 +00:00
|
|
|
options.xrePath = remoteXrePath
|
|
|
|
options.utilityPath = remoteUtilityPath
|
|
|
|
options.profilePath = remoteProfilePath
|
2010-05-27 20:02:15 +00:00
|
|
|
|
2010-02-25 19:10:39 +00:00
|
|
|
def stopWebServer(self, options):
|
2010-05-27 20:02:15 +00:00
|
|
|
self.server.stop()
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
def buildProfile(self, options):
|
2012-01-07 23:41:08 +00:00
|
|
|
if self.localProfile:
|
|
|
|
options.profilePath = self.localProfile
|
2010-02-25 19:10:39 +00:00
|
|
|
manifest = Mochitest.buildProfile(self, options)
|
|
|
|
self.localProfile = options.profilePath
|
2012-01-06 13:37:54 +00:00
|
|
|
self._dm.removeDir(self.remoteProfile)
|
2012-12-28 12:18:22 +00:00
|
|
|
|
|
|
|
# we do not need this for robotium based tests, lets save a LOT of time
|
|
|
|
if options.robocop:
|
|
|
|
shutil.rmtree(os.path.join(options.profilePath, 'webapps'))
|
|
|
|
shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'mochikit@mozilla.org'))
|
|
|
|
shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'worker-test@mozilla.org'))
|
|
|
|
shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'workerbootstrap-test@mozilla.org'))
|
|
|
|
os.remove(os.path.join(options.profilePath, 'userChrome.css'))
|
2013-01-04 18:42:32 +00:00
|
|
|
if os.path.exists(os.path.join(options.profilePath, 'tests.jar')):
|
|
|
|
os.remove(os.path.join(options.profilePath, 'tests.jar'))
|
|
|
|
if os.path.exists(os.path.join(options.profilePath, 'tests.manifest')):
|
|
|
|
os.remove(os.path.join(options.profilePath, 'tests.manifest'))
|
2012-12-28 12:18:22 +00:00
|
|
|
|
Bug 795496 - Make mozdevice raise exceptions on error;r=ahal,jmaher
It turns out that relying on the user to check return codes for every
command was non-intuitive and resulted in many hard to trace bugs.
Now most functinos just return "None", and raise a DMError when there's an
exception. The exception to this are functions like dirExists, which now return
booleans, and throw exceptions on error. This is a fairly major refactor,
and also involved the following internal changes:
* Removed FileError and AgentError exceptions, replaced with DMError
(having to manage three different types of exceptions was confusing,
all the more so when we're raising them)
* Docstrings updated to remove references to return values where no
longer relevant
* pushFile no longer will create a directory to accomodate the file
if it doesn't exist (this makes it consistent with devicemanagerADB)
* dmSUT we validate the file, but assume that we get something back
from the agent, instead of falling back to manual validation in the
case that we didn't
* isDir and dirExists had the same intention, but different
implementations for dmSUT. Replaced the dmSUT impl of getDirectory
with that of isDir's (which was much simpler). Removed
isDir from devicemanager.py, since it wasn't used externally
* killProcess modified to check for process existence before running
(since the actual internal kill command will throw an exception
if the process doesn't exist)
In addition to all this, more unit tests have been added to test these
changes for devicemanagerSUT.
2012-10-04 15:28:07 +00:00
|
|
|
try:
|
|
|
|
self._dm.pushDir(options.profilePath, self.remoteProfile)
|
|
|
|
except devicemanager.DMError:
|
|
|
|
print "Automation Error: Unable to copy profile to device."
|
|
|
|
raise
|
2010-03-15 22:47:34 +00:00
|
|
|
|
2010-02-25 19:10:39 +00:00
|
|
|
options.profilePath = self.remoteProfile
|
|
|
|
return manifest
|
2010-06-24 09:32:01 +00:00
|
|
|
|
2011-07-26 23:13:20 +00:00
|
|
|
def buildURLOptions(self, options, env):
|
2010-02-25 19:10:39 +00:00
|
|
|
self.localLog = options.logFile
|
|
|
|
options.logFile = self.remoteLog
|
2010-11-05 00:01:12 +00:00
|
|
|
options.profilePath = self.localProfile
|
2012-12-07 16:04:01 +00:00
|
|
|
env["MOZ_HIDE_RESULTS_TABLE"] = "1"
|
2011-07-26 23:13:20 +00:00
|
|
|
retVal = Mochitest.buildURLOptions(self, options, env)
|
2012-12-28 12:18:22 +00:00
|
|
|
|
|
|
|
if not options.robocop:
|
|
|
|
#we really need testConfig.js (for browser chrome)
|
|
|
|
try:
|
|
|
|
self._dm.pushDir(options.profilePath, self.remoteProfile)
|
|
|
|
except devicemanager.DMError:
|
|
|
|
print "Automation Error: Unable to copy profile to device."
|
|
|
|
raise
|
2010-11-05 00:01:12 +00:00
|
|
|
|
|
|
|
options.profilePath = self.remoteProfile
|
2010-02-25 19:10:39 +00:00
|
|
|
options.logFile = self.localLog
|
|
|
|
return retVal
|
|
|
|
|
|
|
|
def installChromeFile(self, filename, options):
|
2010-05-27 20:02:15 +00:00
|
|
|
parts = options.app.split('/')
|
|
|
|
if (parts[0] == options.app):
|
|
|
|
return "NO_CHROME_ON_DROID"
|
|
|
|
path = '/'.join(parts[:-1])
|
2010-03-15 22:47:34 +00:00
|
|
|
manifest = path + "/chrome/" + os.path.basename(filename)
|
Bug 795496 - Make mozdevice raise exceptions on error;r=ahal,jmaher
It turns out that relying on the user to check return codes for every
command was non-intuitive and resulted in many hard to trace bugs.
Now most functinos just return "None", and raise a DMError when there's an
exception. The exception to this are functions like dirExists, which now return
booleans, and throw exceptions on error. This is a fairly major refactor,
and also involved the following internal changes:
* Removed FileError and AgentError exceptions, replaced with DMError
(having to manage three different types of exceptions was confusing,
all the more so when we're raising them)
* Docstrings updated to remove references to return values where no
longer relevant
* pushFile no longer will create a directory to accomodate the file
if it doesn't exist (this makes it consistent with devicemanagerADB)
* dmSUT we validate the file, but assume that we get something back
from the agent, instead of falling back to manual validation in the
case that we didn't
* isDir and dirExists had the same intention, but different
implementations for dmSUT. Replaced the dmSUT impl of getDirectory
with that of isDir's (which was much simpler). Removed
isDir from devicemanager.py, since it wasn't used externally
* killProcess modified to check for process existence before running
(since the actual internal kill command will throw an exception
if the process doesn't exist)
In addition to all this, more unit tests have been added to test these
changes for devicemanagerSUT.
2012-10-04 15:28:07 +00:00
|
|
|
try:
|
|
|
|
self._dm.pushFile(filename, manifest)
|
|
|
|
except devicemanager.DMError:
|
|
|
|
print "Automation Error: Unable to install Chrome files on device."
|
|
|
|
raise
|
|
|
|
|
2010-02-25 19:10:39 +00:00
|
|
|
return manifest
|
|
|
|
|
|
|
|
def getLogFilePath(self, logFile):
|
|
|
|
return logFile
|
|
|
|
|
2012-02-09 13:49:00 +00:00
|
|
|
# In the future we could use LogParser: http://hg.mozilla.org/automation/logparser/
|
|
|
|
def addLogData(self):
|
|
|
|
with open(self.localLog) as currentLog:
|
|
|
|
data = currentLog.readlines()
|
|
|
|
|
|
|
|
restart = re.compile('0 INFO SimpleTest START.*')
|
|
|
|
reend = re.compile('([0-9]+) INFO TEST-START . Shutdown.*')
|
2013-01-10 09:24:58 +00:00
|
|
|
refail = re.compile('([0-9]+) INFO TEST-UNEXPECTED-FAIL.*')
|
2012-02-09 13:49:00 +00:00
|
|
|
start_found = False
|
|
|
|
end_found = False
|
2013-01-10 09:24:58 +00:00
|
|
|
fail_found = False
|
2012-02-09 13:49:00 +00:00
|
|
|
for line in data:
|
|
|
|
if reend.match(line):
|
|
|
|
end_found = True
|
|
|
|
start_found = False
|
2013-01-10 09:24:58 +00:00
|
|
|
break
|
2012-02-09 13:49:00 +00:00
|
|
|
|
|
|
|
if start_found and not end_found:
|
|
|
|
# Append the line without the number to increment
|
|
|
|
self.logLines.append(' '.join(line.split(' ')[1:]))
|
|
|
|
|
|
|
|
if restart.match(line):
|
|
|
|
start_found = True
|
2013-01-10 09:24:58 +00:00
|
|
|
if refail.match(line):
|
|
|
|
fail_found = True
|
|
|
|
result = 0
|
|
|
|
if fail_found:
|
|
|
|
result = 1
|
|
|
|
if not end_found:
|
|
|
|
print "ERROR: missing end of test marker (process crashed?)"
|
|
|
|
result = 1
|
|
|
|
return result
|
2012-02-09 13:49:00 +00:00
|
|
|
|
|
|
|
def printLog(self):
|
|
|
|
passed = 0
|
|
|
|
failed = 0
|
|
|
|
todo = 0
|
|
|
|
incr = 1
|
|
|
|
logFile = []
|
|
|
|
logFile.append("0 INFO SimpleTest START")
|
|
|
|
for line in self.logLines:
|
|
|
|
if line.startswith("INFO TEST-PASS"):
|
|
|
|
passed += 1
|
|
|
|
elif line.startswith("INFO TEST-UNEXPECTED"):
|
|
|
|
failed += 1
|
|
|
|
elif line.startswith("INFO TEST-KNOWN"):
|
|
|
|
todo += 1
|
|
|
|
incr += 1
|
|
|
|
|
|
|
|
logFile.append("%s INFO TEST-START | Shutdown" % incr)
|
|
|
|
incr += 1
|
|
|
|
logFile.append("%s INFO Passed: %s" % (incr, passed))
|
|
|
|
incr += 1
|
|
|
|
logFile.append("%s INFO Failed: %s" % (incr, failed))
|
|
|
|
incr += 1
|
|
|
|
logFile.append("%s INFO Todo: %s" % (incr, todo))
|
|
|
|
incr += 1
|
|
|
|
logFile.append("%s INFO SimpleTest FINISHED" % incr)
|
|
|
|
|
|
|
|
# TODO: Consider not printing to stdout because we might be duplicating output
|
|
|
|
print '\n'.join(logFile)
|
|
|
|
with open(self.localLog, 'w') as localLog:
|
|
|
|
localLog.write('\n'.join(logFile))
|
|
|
|
|
|
|
|
if failed > 0:
|
|
|
|
return 1
|
|
|
|
return 0
|
2012-10-16 17:25:23 +00:00
|
|
|
|
2013-03-26 17:31:23 +00:00
|
|
|
def printScreenshot(self):
|
|
|
|
try:
|
|
|
|
image = self._dm.pullFile("/mnt/sdcard/Robotium-Screenshots/robocop-screenshot.jpg")
|
|
|
|
encoded = base64.b64encode(image)
|
|
|
|
print "SCREENSHOT: data:image/jpg;base64,%s" % encoded
|
|
|
|
except:
|
|
|
|
# If the test passes, no screenshot will be generated and
|
|
|
|
# pullFile will fail -- continue silently.
|
|
|
|
pass
|
|
|
|
|
2012-12-04 15:54:37 +00:00
|
|
|
def printDeviceInfo(self):
|
|
|
|
try:
|
|
|
|
logcat = self._dm.getLogcat(filterOutRegexps=fennecLogcatFilters)
|
|
|
|
print ''.join(logcat)
|
|
|
|
print self._dm.getInfo()
|
|
|
|
except devicemanager.DMError:
|
|
|
|
print "WARNING: Error getting device information"
|
|
|
|
|
2012-10-16 17:25:23 +00:00
|
|
|
def buildRobotiumConfig(self, options, browserEnv):
|
|
|
|
deviceRoot = self._dm.getDeviceRoot()
|
|
|
|
fHandle = tempfile.NamedTemporaryFile(suffix='.config',
|
|
|
|
prefix='robotium-',
|
|
|
|
dir=os.getcwd(),
|
|
|
|
delete=False)
|
|
|
|
fHandle.write("profile=%s\n" % (self.remoteProfile))
|
|
|
|
fHandle.write("logfile=%s\n" % (options.remoteLogFile))
|
|
|
|
fHandle.write("host=http://mochi.test:8888/tests\n")
|
|
|
|
fHandle.write("rawhost=http://%s:%s/tests\n" % (options.remoteWebServer, options.httpPort))
|
|
|
|
|
|
|
|
if browserEnv:
|
|
|
|
envstr = ""
|
|
|
|
delim = ""
|
|
|
|
for key, value in browserEnv.items():
|
|
|
|
try:
|
|
|
|
value.index(',')
|
|
|
|
print "Found: Error an ',' in our value, unable to process value."
|
|
|
|
except ValueError, e:
|
|
|
|
envstr += "%s%s=%s" % (delim, key, value)
|
|
|
|
delim = ","
|
|
|
|
|
|
|
|
fHandle.write("envvars=%s\n" % envstr)
|
|
|
|
fHandle.close()
|
|
|
|
|
|
|
|
self._dm.removeFile(os.path.join(deviceRoot, "robotium.config"))
|
|
|
|
self._dm.pushFile(fHandle.name, os.path.join(deviceRoot, "robotium.config"))
|
|
|
|
os.unlink(fHandle.name)
|
|
|
|
|
|
|
|
def buildBrowserEnv(self, options):
|
|
|
|
browserEnv = Mochitest.buildBrowserEnv(self, options)
|
|
|
|
self.buildRobotiumConfig(options, browserEnv)
|
|
|
|
return browserEnv
|
|
|
|
|
2012-02-09 13:49:00 +00:00
|
|
|
|
2010-02-25 19:10:39 +00:00
|
|
|
def main():
|
|
|
|
scriptdir = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
|
2012-03-16 22:36:28 +00:00
|
|
|
auto = RemoteAutomation(None, "fennec")
|
2010-02-25 19:10:39 +00:00
|
|
|
parser = RemoteOptions(auto, scriptdir)
|
|
|
|
options, args = parser.parse_args()
|
2012-01-07 23:41:08 +00:00
|
|
|
if (options.dm_trans == "adb"):
|
2011-05-06 22:17:55 +00:00
|
|
|
if (options.deviceIP):
|
2012-12-20 16:11:11 +00:00
|
|
|
dm = devicemanagerADB.DeviceManagerADB(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
|
2011-05-06 22:17:55 +00:00
|
|
|
else:
|
2012-12-20 16:11:11 +00:00
|
|
|
dm = devicemanagerADB.DeviceManagerADB(deviceRoot=options.remoteTestRoot)
|
2011-05-06 22:17:55 +00:00
|
|
|
else:
|
2012-12-20 16:11:11 +00:00
|
|
|
dm = devicemanagerSUT.DeviceManagerSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
|
2010-02-25 19:10:39 +00:00
|
|
|
auto.setDeviceManager(dm)
|
2010-05-27 20:02:15 +00:00
|
|
|
options = parser.verifyRemoteOptions(options, auto)
|
|
|
|
if (options == None):
|
2010-06-24 09:32:01 +00:00
|
|
|
print "ERROR: Invalid options specified, use --help for a list of valid options"
|
|
|
|
sys.exit(1)
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
productPieces = options.remoteProductName.split('.')
|
|
|
|
if (productPieces != None):
|
2010-06-24 09:32:01 +00:00
|
|
|
auto.setProduct(productPieces[0])
|
2010-02-25 19:10:39 +00:00
|
|
|
else:
|
2010-06-24 09:32:01 +00:00
|
|
|
auto.setProduct(options.remoteProductName)
|
2010-02-25 19:10:39 +00:00
|
|
|
|
|
|
|
mochitest = MochiRemote(auto, dm, options)
|
|
|
|
|
|
|
|
options = parser.verifyOptions(options, mochitest)
|
|
|
|
if (options == None):
|
2010-06-24 09:32:01 +00:00
|
|
|
sys.exit(1)
|
2010-02-25 19:10:39 +00:00
|
|
|
|
2011-07-07 17:10:52 +00:00
|
|
|
logParent = os.path.dirname(options.remoteLogFile)
|
|
|
|
dm.mkDir(logParent);
|
2010-09-29 23:20:33 +00:00
|
|
|
auto.setRemoteLog(options.remoteLogFile)
|
2010-03-15 22:47:34 +00:00
|
|
|
auto.setServerInfo(options.webServer, options.httpPort, options.sslPort)
|
2011-02-23 19:38:56 +00:00
|
|
|
|
2012-07-26 00:45:36 +00:00
|
|
|
print dm.getInfo()
|
|
|
|
|
2011-02-23 19:38:56 +00:00
|
|
|
procName = options.app.split('/')[-1]
|
|
|
|
if (dm.processExist(procName)):
|
2012-01-07 23:41:08 +00:00
|
|
|
dm.killProcess(procName)
|
2012-10-24 17:34:33 +00:00
|
|
|
|
2012-01-07 23:41:08 +00:00
|
|
|
if options.robocop != "":
|
2013-04-08 18:34:45 +00:00
|
|
|
# sut may wait up to 300 s for a robocop am process before returning
|
|
|
|
dm.default_timeout = 320
|
2012-01-07 23:41:08 +00:00
|
|
|
mp = manifestparser.TestManifest(strict=False)
|
|
|
|
# TODO: pull this in dynamically
|
2012-01-07 23:41:08 +00:00
|
|
|
mp.read(options.robocop)
|
2012-01-07 23:41:08 +00:00
|
|
|
robocop_tests = mp.active_tests(exists=False)
|
2013-02-21 14:03:02 +00:00
|
|
|
tests = []
|
|
|
|
my_tests = tests
|
|
|
|
for test in robocop_tests:
|
|
|
|
tests.append(test['name'])
|
|
|
|
|
|
|
|
if options.totalChunks:
|
|
|
|
tests_per_chunk = math.ceil(len(tests) / (options.totalChunks * 1.0))
|
|
|
|
start = int(round((options.thisChunk-1) * tests_per_chunk))
|
|
|
|
end = int(round(options.thisChunk * tests_per_chunk))
|
|
|
|
if end > len(tests):
|
|
|
|
end = len(tests)
|
|
|
|
my_tests = tests[start:end]
|
|
|
|
print "Running tests %d-%d/%d" % ((start+1), end, len(tests))
|
2012-01-07 23:41:08 +00:00
|
|
|
|
2012-10-16 17:25:23 +00:00
|
|
|
deviceRoot = dm.getDeviceRoot()
|
2012-09-12 11:56:31 +00:00
|
|
|
dm.removeFile(os.path.join(deviceRoot, "fennec_ids.txt"))
|
2012-01-11 13:50:10 +00:00
|
|
|
fennec_ids = os.path.abspath("fennec_ids.txt")
|
2012-01-30 18:14:47 +00:00
|
|
|
if not os.path.exists(fennec_ids) and options.robocopIds:
|
|
|
|
fennec_ids = options.robocopIds
|
2012-09-12 11:56:31 +00:00
|
|
|
dm.pushFile(fennec_ids, os.path.join(deviceRoot, "fennec_ids.txt"))
|
2012-01-07 23:41:08 +00:00
|
|
|
options.extraPrefs.append('robocop.logfile="%s/robocop.log"' % deviceRoot)
|
2012-10-06 00:27:12 +00:00
|
|
|
options.extraPrefs.append('browser.search.suggest.enabled=true')
|
|
|
|
options.extraPrefs.append('browser.search.suggest.prompted=true')
|
2013-03-07 21:05:24 +00:00
|
|
|
options.extraPrefs.append('browser.viewport.scaleRatio=100')
|
2013-03-12 18:32:26 +00:00
|
|
|
options.extraPrefs.append('browser.chrome.dynamictoolbar=false')
|
2011-12-31 15:03:36 +00:00
|
|
|
|
2012-01-07 23:41:08 +00:00
|
|
|
if (options.dm_trans == 'adb' and options.robocopPath):
|
2012-10-03 15:07:31 +00:00
|
|
|
dm._checkCmd(["install", "-r", os.path.join(options.robocopPath, "robocop.apk")])
|
2011-12-31 15:03:36 +00:00
|
|
|
|
2012-01-24 14:46:34 +00:00
|
|
|
retVal = None
|
2011-12-31 15:03:36 +00:00
|
|
|
for test in robocop_tests:
|
2012-01-07 23:41:08 +00:00
|
|
|
if options.testPath and options.testPath != test['name']:
|
|
|
|
continue
|
|
|
|
|
2013-02-21 14:03:02 +00:00
|
|
|
if not test['name'] in my_tests:
|
|
|
|
continue
|
|
|
|
|
2012-01-07 23:41:08 +00:00
|
|
|
options.app = "am"
|
2012-09-12 11:56:31 +00:00
|
|
|
options.browserArgs = ["instrument", "-w", "-e", "deviceroot", deviceRoot, "-e", "class"]
|
2012-12-28 12:18:22 +00:00
|
|
|
options.browserArgs.append("%s.tests.%s" % (options.remoteappname, test['name']))
|
|
|
|
options.browserArgs.append("org.mozilla.roboexample.test/%s.FennecInstrumentationTestRunner" % options.remoteappname)
|
2012-01-07 23:41:08 +00:00
|
|
|
|
|
|
|
try:
|
2013-03-26 17:31:23 +00:00
|
|
|
dm.removeDir("/mnt/sdcard/Robotium-Screenshots")
|
2012-06-13 18:20:43 +00:00
|
|
|
dm.recordLogcat()
|
2012-11-20 15:24:28 +00:00
|
|
|
result = mochitest.runTests(options)
|
2012-11-21 18:57:11 +00:00
|
|
|
if result != 0:
|
|
|
|
print "ERROR: runTests() exited with code %s" % result
|
2013-01-10 09:24:58 +00:00
|
|
|
log_result = mochitest.addLogData()
|
|
|
|
if result != 0 or log_result != 0:
|
2012-12-04 15:54:37 +00:00
|
|
|
mochitest.printDeviceInfo()
|
2013-03-26 17:31:23 +00:00
|
|
|
mochitest.printScreenshot()
|
2012-11-20 15:24:28 +00:00
|
|
|
# Ensure earlier failures aren't overwritten by success on this run
|
|
|
|
if retVal is None or retVal == 0:
|
|
|
|
retVal = result
|
2012-01-07 23:41:08 +00:00
|
|
|
except:
|
2012-11-05 13:03:55 +00:00
|
|
|
print "Automation Error: Exception caught while running tests"
|
|
|
|
traceback.print_exc()
|
2012-01-07 23:41:08 +00:00
|
|
|
mochitest.stopWebServer(options)
|
|
|
|
mochitest.stopWebSocketServer(options)
|
2012-01-11 13:50:10 +00:00
|
|
|
try:
|
2012-10-24 17:34:33 +00:00
|
|
|
mochitest.cleanup(None, options)
|
|
|
|
except devicemanager.DMError:
|
|
|
|
# device error cleaning up... oh well!
|
2012-01-11 13:50:10 +00:00
|
|
|
pass
|
2012-10-24 17:34:33 +00:00
|
|
|
retVal = 1
|
|
|
|
break
|
2012-01-24 14:46:34 +00:00
|
|
|
if retVal is None:
|
|
|
|
print "No tests run. Did you pass an invalid TEST_PATH?"
|
2012-02-09 13:49:00 +00:00
|
|
|
retVal = 1
|
2012-11-21 18:53:48 +00:00
|
|
|
else:
|
2012-10-24 17:34:33 +00:00
|
|
|
# if we didn't have some kind of error running the tests, make
|
|
|
|
# sure the tests actually passed
|
2013-01-04 18:42:22 +00:00
|
|
|
print "INFO | runtests.py | Test summary: start."
|
2012-11-21 18:53:48 +00:00
|
|
|
overallResult = mochitest.printLog()
|
2013-01-04 18:42:22 +00:00
|
|
|
print "INFO | runtests.py | Test summary: end."
|
2012-11-21 18:53:48 +00:00
|
|
|
if retVal == 0:
|
|
|
|
retVal = overallResult
|
2011-12-31 15:03:36 +00:00
|
|
|
else:
|
2012-01-11 13:50:10 +00:00
|
|
|
try:
|
2012-10-24 17:34:33 +00:00
|
|
|
dm.recordLogcat()
|
|
|
|
retVal = mochitest.runTests(options)
|
2012-01-11 13:50:10 +00:00
|
|
|
except:
|
2012-11-05 13:03:55 +00:00
|
|
|
print "Automation Error: Exception caught while running tests"
|
|
|
|
traceback.print_exc()
|
2012-10-24 17:34:33 +00:00
|
|
|
mochitest.stopWebServer(options)
|
|
|
|
mochitest.stopWebSocketServer(options)
|
|
|
|
try:
|
|
|
|
mochitest.cleanup(None, options)
|
|
|
|
except devicemanager.DMError:
|
|
|
|
# device error cleaning up... oh well!
|
|
|
|
pass
|
|
|
|
retVal = 1
|
|
|
|
|
2012-12-04 15:54:37 +00:00
|
|
|
mochitest.printDeviceInfo()
|
2012-01-07 23:41:08 +00:00
|
|
|
|
2011-09-26 11:41:19 +00:00
|
|
|
sys.exit(retVal)
|
2012-12-04 15:54:37 +00:00
|
|
|
|
2010-02-25 19:10:39 +00:00
|
|
|
if __name__ == "__main__":
|
2010-06-24 09:32:01 +00:00
|
|
|
main()
|