2012-05-21 04:12:37 -07: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 10:51:17 -07:00
2010-02-25 11:10:39 -08:00
import sys
import os
import time
2010-05-27 13:02:15 -07:00
import tempfile
2012-02-09 05:49:00 -08:00
import re
2012-11-05 05:03:55 -08:00
import traceback
2012-12-28 04:18:22 -08:00
import shutil
2013-02-21 06:03:02 -08:00
import math
2013-03-26 10:31:23 -07:00
import base64
2010-02-25 11:10:39 -08:00
2013-08-26 14:17:51 -07:00
sys . path . insert ( 0 , os . path . abspath ( os . path . realpath ( os . path . dirname ( __file__ ) ) ) )
2010-02-25 11:10:39 -08:00
from automation import Automation
2012-11-12 13:57:13 -08:00
from remoteautomation import RemoteAutomation , fennecLogcatFilters
2010-02-25 11:10:39 -08:00
from runtests import Mochitest
2010-05-27 13:02:15 -07:00
from runtests import MochitestServer
2013-07-26 11:40:04 -07:00
from mochitest_options import MochitestOptions
2010-02-25 11:10:39 -08:00
2013-05-03 10:37:59 -07:00
import devicemanager
import droid
2011-12-31 07:03:36 -08:00
import manifestparser
2013-10-08 12:14:38 -07:00
import mozinfo
2013-09-05 09:14:54 -07:00
import mozlog
2014-04-21 08:03:51 -07:00
import moznetwork
2013-09-05 09:14:54 -07:00
2014-04-16 07:29:39 -07:00
SCRIPT_DIR = os . path . abspath ( os . path . realpath ( os . path . dirname ( __file__ ) ) )
2013-09-05 09:14:54 -07:00
log = mozlog . getLogger ( ' Mochi-Remote ' )
2010-02-25 11:10:39 -08:00
class RemoteOptions ( MochitestOptions ) :
2013-07-26 11:40:04 -07:00
def __init__ ( self , automation , * * kwargs ) :
2010-02-25 11:10:39 -08:00
defaults = { }
2013-09-23 07:47:48 -07:00
self . _automation = automation or Automation ( )
MochitestOptions . __init__ ( self )
2010-02-25 11:10:39 -08:00
2010-06-24 02:32:01 -07: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 11:10:39 -08: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 15:17:55 -07: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 11:10:39 -08:00
self . add_option ( " --devicePort " , action = " store " ,
type = " string " , dest = " devicePort " ,
help = " port of remote device to test " )
2010-05-27 13:02:15 -07:00
defaults [ " devicePort " ] = 20701
2010-02-25 11:10:39 -08:00
2010-06-24 02:32:01 -07:00
self . add_option ( " --remote-product-name " , action = " store " ,
2010-02-25 11:10:39 -08:00
type = " string " , dest = " remoteProductName " ,
2010-05-27 13:02:15 -07:00
help = " The executable ' s name of remote product to test - either fennec or firefox, defaults to fennec " )
defaults [ " remoteProductName " ] = " fennec "
2010-02-25 11:10:39 -08:00
self . add_option ( " --remote-logfile " , action = " store " ,
type = " string " , dest = " remoteLogFile " ,
2010-05-27 13:02:15 -07:00
help = " Name of log file on the device relative to the device root. PLEASE ONLY USE A FILENAME. " )
2010-02-25 11:10:39 -08:00
defaults [ " remoteLogFile " ] = None
2010-03-13 10:34:19 -08: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 14:07:14 -07:00
help = " http port of the remote web server " )
2010-03-13 10:34:19 -08:00
defaults [ " httpPort " ] = automation . DEFAULT_HTTP_PORT
self . add_option ( " --ssl-port " , action = " store " ,
type = " string " , dest = " sslPort " ,
2012-06-05 14:07:14 -07:00
help = " ssl port of the remote web server " )
2010-03-13 10:34:19 -08:00
defaults [ " sslPort " ] = automation . DEFAULT_SSL_PORT
2010-02-25 11:10:39 -08:00
2013-05-20 11:39:50 -07:00
self . add_option ( " --robocop-ini " , action = " store " ,
type = " string " , dest = " robocopIni " ,
help = " name of the .ini file containing the list of tests to run " )
defaults [ " robocopIni " ] = " "
2011-12-31 07:03:36 -08:00
self . add_option ( " --robocop " , action = " store " ,
type = " string " , dest = " robocop " ,
2013-05-20 11:39:50 -07:00
help = " name of the .ini file containing the list of tests to run. [DEPRECATED- please use --robocop-ini " )
2011-12-31 07:03:36 -08:00
defaults [ " robocop " ] = " "
2013-05-20 11:39:50 -07:00
self . add_option ( " --robocop-apk " , action = " store " ,
type = " string " , dest = " robocopApk " ,
help = " name of the Robocop APK to use for ADB test running " )
defaults [ " robocopApk " ] = " "
2012-01-07 15:41:08 -08:00
self . add_option ( " --robocop-path " , action = " store " ,
type = " string " , dest = " robocopPath " ,
2013-05-20 11:39:50 -07:00
help = " Path to the folder where robocop.apk is located at. Primarily used for ADB test running. [DEPRECATED- please use --robocop-apk] " )
2012-01-07 15:41:08 -08:00
defaults [ " robocopPath " ] = " "
2012-01-30 10:14:47 -08: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 08:11:11 -08: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 13:02:15 -07:00
defaults [ " remoteTestRoot " ] = None
2012-12-20 08:11:11 -08:00
2010-02-25 11:10:39 -08:00
defaults [ " logFile " ] = " mochitest.log "
defaults [ " autorun " ] = True
defaults [ " closeWhenDone " ] = True
defaults [ " testPath " ] = " "
2010-05-27 13:02:15 -07:00
defaults [ " app " ] = None
2013-09-12 08:02:48 -07:00
defaults [ " utilityPath " ] = None
2010-02-25 11:10:39 -08:00
self . set_defaults ( * * defaults )
2010-05-27 13:02:15 -07:00
def verifyRemoteOptions ( self , options , automation ) :
2012-12-20 08:11:11 -08:00
if not options . remoteTestRoot :
options . remoteTestRoot = automation . _devicemanager . getDeviceRoot ( )
2010-03-13 10:34:19 -08:00
2010-12-09 14:47:21 -08:00
if options . remoteWebServer == None :
if os . name != " nt " :
2014-04-21 08:03:51 -07:00
options . remoteWebServer = moznetwork . get_ip ( )
2010-12-09 14:47:21 -08:00
else :
2013-09-05 09:14:54 -07:00
log . error ( " you must specify a --remote-webserver=<ip address> " )
2010-12-09 14:47:21 -08:00
return None
2010-05-27 13:02:15 -07:00
options . webServer = options . remoteWebServer
2010-02-25 11:10:39 -08:00
if ( options . deviceIP == None ) :
2013-09-05 09:14:54 -07:00
log . error ( " you must provide a device IP " )
2010-06-24 02:32:01 -07:00
return None
2010-02-25 11:10:39 -08:00
if ( options . remoteLogFile == None ) :
2011-07-07 10:10:52 -07:00
options . remoteLogFile = options . remoteTestRoot + ' /logs/mochitest.log '
2010-09-29 16:20:33 -07:00
if ( options . remoteLogFile . count ( ' / ' ) < 1 ) :
2010-12-09 14:47:21 -08:00
options . remoteLogFile = options . remoteTestRoot + ' / ' + options . remoteLogFile
2010-06-15 13:02:17 -07:00
2010-06-24 02:32:01 -07:00
# remoteAppPath or app must be specified to find the product to launch
if ( options . remoteAppPath and options . app ) :
2013-09-05 09:14:54 -07:00
log . error ( " You cannot specify both the remoteAppPath and the app setting " )
2010-06-24 02:32:01 -07:00
return None
elif ( options . remoteAppPath ) :
options . app = options . remoteTestRoot + " / " + options . remoteAppPath
elif ( options . app == None ) :
# Neither remoteAppPath nor app are set -- error
2013-09-05 09:14:54 -07:00
log . error ( " You must specify either appPath or app " )
2010-06-24 02:32:01 -07:00
return None
2010-05-27 13:02:15 -07:00
# Only reset the xrePath if it wasn't provided
if ( options . xrePath == None ) :
2013-09-12 08:02:48 -07:00
options . xrePath = options . utilityPath
2010-05-27 13:02:15 -07:00
2011-04-19 15:17:01 -07:00
if ( options . pidFile != " " ) :
f = open ( options . pidFile , ' w ' )
f . write ( " %s " % os . getpid ( ) )
f . close ( )
2013-05-20 11:39:50 -07:00
# Robocop specific deprecated options.
if options . robocop :
if options . robocopIni :
2013-09-05 09:14:54 -07:00
log . error ( " can not use deprecated --robocop and replacement --robocop-ini together " )
2013-05-20 11:39:50 -07:00
return None
options . robocopIni = options . robocop
del options . robocop
if options . robocopPath :
if options . robocopApk :
2013-09-05 09:14:54 -07:00
log . error ( " can not use deprecated --robocop-path and replacement --robocop-apk together " )
2013-05-20 11:39:50 -07:00
return None
options . robocopApk = os . path . join ( options . robocopPath , ' robocop.apk ' )
del options . robocopPath
2012-01-07 15:41:08 -08:00
# Robocop specific options
2013-05-20 11:39:50 -07:00
if options . robocopIni != " " :
if not os . path . exists ( options . robocopIni ) :
2013-09-05 09:14:54 -07:00
log . error ( " Unable to find specified robocop .ini manifest ' %s ' " , options . robocopIni )
2012-01-07 15:41:08 -08:00
return None
2013-05-20 11:39:50 -07:00
options . robocopIni = os . path . abspath ( options . robocopIni )
2012-01-07 15:41:08 -08:00
2013-05-20 11:39:50 -07:00
if options . robocopApk != " " :
if not os . path . exists ( options . robocopApk ) :
2013-09-05 09:14:54 -07:00
log . error ( " Unable to find robocop APK ' %s ' " , options . robocopApk )
2012-01-07 15:41:08 -08:00
return None
2013-05-20 11:39:50 -07:00
options . robocopApk = os . path . abspath ( options . robocopApk )
2012-01-07 15:41:08 -08:00
2012-01-30 10:14:47 -08:00
if options . robocopIds != " " :
if not os . path . exists ( options . robocopIds ) :
2013-09-05 09:14:54 -07:00
log . error ( " Unable to find specified robocop IDs file ' %s ' " , options . robocopIds )
2012-01-30 10:14:47 -08:00
return None
options . robocopIds = os . path . abspath ( options . robocopIds )
2012-12-28 04:18:22 -08:00
# allow us to keep original application around for cleanup while running robocop via 'am'
options . remoteappname = options . app
2010-05-27 13:02:15 -07:00
return options
def verifyOptions ( self , options , mochitest ) :
# since we are reusing verifyOptions, it will exit if App is not found
temp = options . app
2013-08-26 14:17:51 -07:00
options . app = __file__
2010-05-27 13:02:15 -07:00
tempPort = options . httpPort
tempSSL = options . sslPort
tempIP = options . webServer
2013-11-21 08:33:43 -08:00
# We are going to override this option later anyway, just pretend
# like it's not set for verification purposes.
options . dumpOutputDirectory = None
2010-05-27 13:02:15 -07:00
options = MochitestOptions . verifyOptions ( self , options , mochitest )
options . webServer = tempIP
options . app = temp
options . sslPort = tempSSL
options . httpPort = tempPort
2010-02-25 11:10:39 -08:00
2014-03-14 11:25:41 -07:00
return options
2010-02-25 11:10:39 -08:00
class MochiRemote ( Mochitest ) :
_automation = None
_dm = None
2012-01-07 15:41:08 -08:00
localProfile = None
2012-02-09 05:49:00 -08:00
logLines = [ ]
2010-02-25 11:10:39 -08:00
def __init__ ( self , automation , devmgr , options ) :
self . _automation = automation
2013-09-23 07:47:48 -07:00
Mochitest . __init__ ( self )
2010-02-25 11:10:39 -08:00
self . _dm = devmgr
2013-09-23 07:47:48 -07:00
self . environment = self . _automation . environment
2010-05-27 13:02:15 -07:00
self . remoteProfile = options . remoteTestRoot + " /profile "
2011-10-06 07:51:03 -07:00
self . _automation . setRemoteProfile ( self . remoteProfile )
2010-02-25 11:10:39 -08:00
self . remoteLog = options . remoteLogFile
2012-10-03 08:07:31 -07:00
self . localLog = options . logFile
2013-05-16 13:32:52 -07:00
self . _automation . deleteANRs ( )
2014-03-14 11:25:41 -07:00
self . certdbNew = True
2010-02-25 11:10:39 -08:00
2014-06-03 08:19:28 -07:00
def cleanup ( self , options ) :
2012-10-15 07:15:19 -07:00
if self . _dm . fileExists ( self . remoteLog ) :
self . _dm . getFile ( self . remoteLog , self . localLog )
self . _dm . removeFile ( self . remoteLog )
else :
2013-09-05 09:14:54 -07:00
log . warn ( " Unable to retrieve log file ( %s ) from remote device " ,
self . remoteLog )
2010-02-25 11:10:39 -08:00
self . _dm . removeDir ( self . remoteProfile )
2014-06-03 08:19:28 -07:00
Mochitest . cleanup ( self , options )
2010-02-25 11:10:39 -08:00
2010-05-27 13:02:15 -07:00
def findPath ( self , paths , filename = None ) :
2010-06-24 02:32:01 -07: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 13:02:15 -07:00
2014-03-14 11:25:41 -07:00
def makeLocalAutomation ( self ) :
2010-06-24 02:32:01 -07:00
localAutomation = Automation ( )
2011-02-24 11:45:42 -08: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 ' ) :
2014-03-14 11:25:41 -07:00
localAutomation . IS_MAC = True
2011-02-24 11:45:42 -08:00
elif ( hostos == ' linux ' or hostos == ' linux2 ' ) :
2014-03-14 11:25:41 -07:00
localAutomation . IS_LINUX = True
localAutomation . UNIXISH = True
2011-02-24 11:45:42 -08:00
elif ( hostos == ' win32 ' or hostos == ' win64 ' ) :
2014-03-14 11:25:41 -07:00
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
2010-06-24 02:32:01 -07:00
2014-03-14 11:25:41 -07:00
localAutomation = self . makeLocalAutomation ( )
paths = [
options . xrePath ,
localAutomation . DIST_BIN ,
self . _automation . _product ,
os . path . join ( ' .. ' , self . _automation . _product )
]
2010-06-24 02:32:01 -07:00
options . xrePath = self . findPath ( paths )
if options . xrePath == None :
2013-09-05 09:14:54 -07:00
log . error ( " unable to find xulrunner path for %s , please specify with --xre-path " , os . name )
2010-06-24 02:32:01 -07:00
sys . exit ( 1 )
xpcshell = " xpcshell "
if ( os . name == " nt " ) :
xpcshell + = " .exe "
2014-03-14 11:25:41 -07:00
2013-09-12 08:02:48 -07:00
if options . utilityPath :
paths = [ options . utilityPath , options . xrePath ]
else :
paths = [ options . xrePath ]
2010-06-24 02:32:01 -07:00
options . utilityPath = self . findPath ( paths , xpcshell )
2014-03-14 11:25:41 -07:00
2010-06-24 02:32:01 -07:00
if options . utilityPath == None :
2013-09-05 09:14:54 -07:00
log . error ( " unable to find utility path for %s , please specify with --utility-path " , os . name )
2010-06-24 02:32:01 -07:00
sys . exit ( 1 )
2013-01-25 11:35:15 -08:00
xpcshell_path = os . path . join ( options . utilityPath , xpcshell )
if localAutomation . elf_arm ( xpcshell_path ) :
2013-09-05 09:14:56 -07:00
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 )
2013-01-25 11:35:15 -08:00
2012-01-07 15:41:08 -08:00
if self . localProfile :
options . profilePath = self . localProfile
2014-03-14 11:25:41 -07:00
else :
2014-06-23 02:24:00 -07:00
options . profilePath = None
2014-03-14 11:25:41 -07:00
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 )
Mochitest . startServers ( self , options , debuggerInfo )
restoreRemotePaths ( )
def buildProfile ( self , options ) :
restoreRemotePaths = self . switchToLocalPaths ( options )
2010-02-25 11:10:39 -08:00
manifest = Mochitest . buildProfile ( self , options )
self . localProfile = options . profilePath
2012-01-06 05:37:54 -08:00
self . _dm . removeDir ( self . remoteProfile )
2012-12-28 04:18:22 -08:00
# we do not need this for robotium based tests, lets save a LOT of time
2013-05-20 11:39:50 -07:00
if options . robocopIni :
2012-12-28 04:18:22 -08:00
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 ' ) )
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 08:28:07 -07:00
try :
self . _dm . pushDir ( options . profilePath , self . remoteProfile )
except devicemanager . DMError :
2013-09-05 09:14:54 -07:00
log . error ( " Automation Error: Unable to copy profile to device. " )
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 08:28:07 -07:00
raise
2010-03-15 15:47:34 -07:00
2014-03-14 11:25:41 -07:00
restoreRemotePaths ( )
2010-02-25 11:10:39 -08:00
options . profilePath = self . remoteProfile
return manifest
2014-03-14 11:25:41 -07:00
2011-07-26 16:13:20 -07:00
def buildURLOptions ( self , options , env ) :
2010-02-25 11:10:39 -08:00
self . localLog = options . logFile
options . logFile = self . remoteLog
2010-11-04 17:01:12 -07:00
options . profilePath = self . localProfile
2012-12-07 08:04:01 -08:00
env [ " MOZ_HIDE_RESULTS_TABLE " ] = " 1 "
2011-07-26 16:13:20 -07:00
retVal = Mochitest . buildURLOptions ( self , options , env )
2012-12-28 04:18:22 -08:00
2013-05-20 11:39:50 -07:00
if not options . robocopIni :
2012-12-28 04:18:22 -08:00
#we really need testConfig.js (for browser chrome)
try :
self . _dm . pushDir ( options . profilePath , self . remoteProfile )
except devicemanager . DMError :
2013-09-05 09:14:54 -07:00
log . error ( " Automation Error: Unable to copy profile to device. " )
2012-12-28 04:18:22 -08:00
raise
2010-11-04 17:01:12 -07:00
options . profilePath = self . remoteProfile
2010-02-25 11:10:39 -08:00
options . logFile = self . localLog
return retVal
2014-03-18 08:03:51 -07:00
def buildTestPath ( self , options ) :
if options . robocopIni != " " :
# Skip over manifest building if we just want to run
# robocop tests.
return self . buildTestURL ( options )
else :
return super ( MochiRemote , self ) . buildTestPath ( options )
2010-02-25 11:10:39 -08:00
def installChromeFile ( self , filename , options ) :
2010-05-27 13:02:15 -07:00
parts = options . app . split ( ' / ' )
if ( parts [ 0 ] == options . app ) :
return " NO_CHROME_ON_DROID "
path = ' / ' . join ( parts [ : - 1 ] )
2010-03-15 15:47:34 -07: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 08:28:07 -07:00
try :
self . _dm . pushFile ( filename , manifest )
except devicemanager . DMError :
2013-09-05 09:14:54 -07:00
log . error ( " Automation Error: Unable to install Chrome files on device. " )
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 08:28:07 -07:00
raise
2010-02-25 11:10:39 -08:00
return manifest
2014-03-14 11:25:41 -07:00
def getLogFilePath ( self , logFile ) :
2010-02-25 11:10:39 -08:00
return logFile
2012-02-09 05:49:00 -08: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 01:24:58 -08:00
refail = re . compile ( ' ([0-9]+) INFO TEST-UNEXPECTED-FAIL.* ' )
2012-02-09 05:49:00 -08:00
start_found = False
end_found = False
2013-01-10 01:24:58 -08:00
fail_found = False
2012-02-09 05:49:00 -08:00
for line in data :
if reend . match ( line ) :
end_found = True
start_found = False
2013-01-10 01:24:58 -08:00
break
2012-02-09 05:49:00 -08: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 01:24:58 -08:00
if refail . match ( line ) :
fail_found = True
result = 0
if fail_found :
result = 1
if not end_found :
2013-09-19 05:20:17 -07:00
log . error ( " Automation Error: Missing end of test marker (process crashed?) " )
2013-01-10 01:24:58 -08:00
result = 1
return result
2012-02-09 05:49:00 -08:00
def printLog ( self ) :
passed = 0
failed = 0
todo = 0
incr = 1
2014-03-14 11:25:41 -07:00
logFile = [ ]
2012-02-09 05:49:00 -08:00
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 10:25:23 -07:00
2013-09-27 14:04:16 -07:00
def printScreenshots ( self , screenShotDir ) :
# TODO: This can be re-written after completion of bug 749421
if not self . _dm . dirExists ( screenShotDir ) :
log . info ( " SCREENSHOT: No ScreenShots directory available: " + screenShotDir )
return
printed = 0
for name in self . _dm . listFiles ( screenShotDir ) :
fullName = screenShotDir + " / " + name
log . info ( " SCREENSHOT: FOUND: [ %s ] " , fullName )
try :
image = self . _dm . pullFile ( fullName )
encoded = base64 . b64encode ( image )
log . info ( " SCREENSHOT: data:image/jpg;base64, %s " , encoded )
printed + = 1
except :
log . info ( " SCREENSHOT: Could not be parsed " )
pass
log . info ( " SCREENSHOT: TOTAL PRINTED: [ %s ] " , printed )
2013-03-26 10:31:23 -07:00
2013-06-24 17:15:40 -07:00
def printDeviceInfo ( self , printLogcat = False ) :
2012-12-04 07:54:37 -08:00
try :
2013-06-24 17:15:40 -07:00
if printLogcat :
logcat = self . _dm . getLogcat ( filterOutRegexps = fennecLogcatFilters )
2013-09-05 09:14:54 -07:00
log . info ( ' \n ' + ( ' ' . join ( logcat ) ) )
log . info ( " Device info: %s " , self . _dm . getInfo ( ) )
log . info ( " Test root: %s " , self . _dm . getDeviceRoot ( ) )
2012-12-04 07:54:37 -08:00
except devicemanager . DMError :
2013-09-05 09:14:54 -07:00
log . warn ( " Error getting device information " )
2012-12-04 07:54:37 -08:00
2012-10-16 10:25:23 -07: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 ( ' , ' )
2013-09-23 07:47:48 -07:00
log . error ( " buildRobotiumConfig: browserEnv - Found a ' , ' in our value, unable to process value. key= %s ,value= %s " , key , value )
log . error ( " browserEnv= %s " , browserEnv )
2012-10-16 10:25:23 -07:00
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 )
2013-09-16 11:44:25 -07:00
def buildBrowserEnv ( self , options , debugger = False ) :
browserEnv = Mochitest . buildBrowserEnv ( self , options , debugger = debugger )
2012-10-16 10:25:23 -07:00
self . buildRobotiumConfig ( options , browserEnv )
return browserEnv
2013-09-23 07:47:48 -07:00
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
2014-03-14 11:25:41 -07:00
# We're handling ssltunnel, so we should lie to automation.py to avoid
# it trying to set up ssltunnel as well
kwargs [ ' runSSLTunnel ' ] = False
2013-09-23 07:47:48 -07:00
return self . _automation . runApp ( * args , * * kwargs )
2010-02-25 11:10:39 -08:00
def main ( ) :
2012-03-16 15:36:28 -07:00
auto = RemoteAutomation ( None , " fennec " )
2013-07-26 11:40:04 -07:00
parser = RemoteOptions ( auto )
2010-02-25 11:10:39 -08:00
options , args = parser . parse_args ( )
2013-05-20 11:39:50 -07:00
2012-01-07 15:41:08 -08:00
if ( options . dm_trans == " adb " ) :
2011-05-06 15:17:55 -07:00
if ( options . deviceIP ) :
2013-05-03 10:37:59 -07:00
dm = droid . DroidADB ( options . deviceIP , options . devicePort , deviceRoot = options . remoteTestRoot )
2011-05-06 15:17:55 -07:00
else :
2013-05-03 10:37:59 -07:00
dm = droid . DroidADB ( deviceRoot = options . remoteTestRoot )
2011-05-06 15:17:55 -07:00
else :
2013-05-03 10:37:59 -07:00
dm = droid . DroidSUT ( options . deviceIP , options . devicePort , deviceRoot = options . remoteTestRoot )
2010-02-25 11:10:39 -08:00
auto . setDeviceManager ( dm )
2010-05-27 13:02:15 -07:00
options = parser . verifyRemoteOptions ( options , auto )
if ( options == None ) :
2013-09-05 09:14:54 -07:00
log . error ( " Invalid options specified, use --help for a list of valid options " )
2010-06-24 02:32:01 -07:00
sys . exit ( 1 )
2010-02-25 11:10:39 -08:00
productPieces = options . remoteProductName . split ( ' . ' )
if ( productPieces != None ) :
2010-06-24 02:32:01 -07:00
auto . setProduct ( productPieces [ 0 ] )
2010-02-25 11:10:39 -08:00
else :
2010-06-24 02:32:01 -07:00
auto . setProduct ( options . remoteProductName )
2014-02-02 07:11:22 -08:00
auto . setAppName ( options . remoteappname )
2010-02-25 11:10:39 -08:00
mochitest = MochiRemote ( auto , dm , options )
options = parser . verifyOptions ( options , mochitest )
if ( options == None ) :
2010-06-24 02:32:01 -07:00
sys . exit ( 1 )
2014-03-14 11:25:41 -07:00
2011-07-07 10:10:52 -07:00
logParent = os . path . dirname ( options . remoteLogFile )
dm . mkDir ( logParent ) ;
2010-09-29 16:20:33 -07:00
auto . setRemoteLog ( options . remoteLogFile )
2010-03-15 15:47:34 -07:00
auto . setServerInfo ( options . webServer , options . httpPort , options . sslPort )
2011-02-23 11:38:56 -08:00
2013-06-24 17:15:40 -07:00
mochitest . printDeviceInfo ( )
2012-07-25 17:45:36 -07:00
2013-10-08 12:14:38 -07:00
# 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
2013-11-13 11:48:10 -08:00
deviceRoot = dm . getDeviceRoot ( )
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
2013-11-21 08:33:43 -08:00
options . dumpOutputDirectory = deviceRoot
2011-02-23 11:38:56 -08:00
procName = options . app . split ( ' / ' ) [ - 1 ]
2014-02-02 07:11:22 -08:00
dm . killProcess ( procName )
2012-10-24 10:34:33 -07:00
2013-05-20 11:39:50 -07:00
if options . robocopIni != " " :
2013-04-08 11:34:45 -07:00
# sut may wait up to 300 s for a robocop am process before returning
dm . default_timeout = 320
2012-01-07 15:41:08 -08:00
mp = manifestparser . TestManifest ( strict = False )
# TODO: pull this in dynamically
2013-05-20 11:39:50 -07:00
mp . read ( options . robocopIni )
2013-10-08 12:14:38 -07:00
robocop_tests = mp . active_tests ( exists = False , * * mozinfo . info )
2013-02-21 06:03:02 -08: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 ]
2013-09-05 09:14:54 -07:00
log . info ( " Running tests %d - %d / %d " , start + 1 , end , len ( tests ) )
2012-01-07 15:41:08 -08:00
2012-09-12 04:56:31 -07:00
dm . removeFile ( os . path . join ( deviceRoot , " fennec_ids.txt " ) )
2014-04-16 07:29:39 -07:00
fennec_ids = os . path . abspath ( os . path . join ( SCRIPT_DIR , " fennec_ids.txt " ) )
2012-01-30 10:14:47 -08:00
if not os . path . exists ( fennec_ids ) and options . robocopIds :
fennec_ids = options . robocopIds
2012-09-12 04:56:31 -07:00
dm . pushFile ( fennec_ids , os . path . join ( deviceRoot , " fennec_ids.txt " ) )
2012-10-05 17:27:12 -07:00
options . extraPrefs . append ( ' browser.search.suggest.enabled=true ' )
options . extraPrefs . append ( ' browser.search.suggest.prompted=true ' )
2013-10-22 19:34:03 -07:00
options . extraPrefs . append ( ' layout.css.devPixelsPerPx=1.0 ' )
2013-03-12 11:32:26 -07:00
options . extraPrefs . append ( ' browser.chrome.dynamictoolbar=false ' )
2014-01-30 10:53:33 -08:00
options . extraPrefs . append ( ' browser.snippets.enabled=false ' )
2011-12-31 07:03:36 -08:00
2013-05-20 11:39:50 -07:00
if ( options . dm_trans == ' adb ' and options . robocopApk ) :
2013-10-08 12:14:39 -07:00
dm . _checkCmd ( [ " install " , " -r " , options . robocopApk ] )
2011-12-31 07:03:36 -08:00
2012-01-24 06:46:34 -08:00
retVal = None
2011-12-31 07:03:36 -08:00
for test in robocop_tests :
2012-01-07 15:41:08 -08:00
if options . testPath and options . testPath != test [ ' name ' ] :
continue
2013-02-21 06:03:02 -08:00
if not test [ ' name ' ] in my_tests :
continue
2013-10-08 12:14:39 -07:00
if ' disabled ' in test :
log . info ( ' TEST-INFO | skipping %s | %s ' % ( test [ ' name ' ] , test [ ' disabled ' ] ) )
continue
2013-07-30 05:30:40 -07:00
# When running in a loop, we need to create a fresh profile for each cycle
if mochitest . localProfile :
options . profilePath = mochitest . localProfile
os . system ( " rm -Rf %s " % options . profilePath )
2014-06-23 02:24:00 -07:00
options . profilePath = None
2013-07-30 05:30:40 -07:00
mochitest . localProfile = options . profilePath
2012-01-07 15:41:08 -08:00
options . app = " am "
2012-09-12 04:56:31 -07:00
options . browserArgs = [ " instrument " , " -w " , " -e " , " deviceroot " , deviceRoot , " -e " , " class " ]
2013-11-07 08:18:51 -08:00
options . browserArgs . append ( " org.mozilla.gecko.tests. %s " % test [ ' name ' ] )
options . browserArgs . append ( " org.mozilla.roboexample.test/org.mozilla.gecko.FennecInstrumentationTestRunner " )
2012-01-07 15:41:08 -08:00
2013-03-18 02:15:17 -07:00
# If the test is for checking the import from bookmarks then make sure there is data to import
if test [ ' name ' ] == " testImportFromAndroid " :
2014-03-14 11:25:41 -07:00
2013-03-18 02:15:17 -07:00
# Get the OS so we can run the insert in the apropriate database and following the correct table schema
osInfo = dm . getInfo ( " os " )
devOS = " " . join ( osInfo [ ' os ' ] )
if ( " pandaboard " in devOS ) :
delete = [ ' execsu ' , ' sqlite3 ' , " /data/data/com.android.browser/databases/browser2.db \' delete from bookmarks where _id > 14; \' " ]
else :
delete = [ ' execsu ' , ' sqlite3 ' , " /data/data/com.android.browser/databases/browser.db \' delete from bookmarks where _id > 14; \' " ]
if ( options . dm_trans == " sut " ) :
dm . _runCmds ( [ { " cmd " : " " . join ( delete ) } ] )
# Insert the bookmarks
2013-09-05 09:14:54 -07:00
log . info ( " Insert bookmarks in the default android browser database " )
2013-03-18 02:15:17 -07:00
for i in range ( 20 ) :
if ( " pandaboard " in devOS ) :
cmd = [ ' execsu ' , ' sqlite3 ' , " /data/data/com.android.browser/databases/browser2.db ' insert or replace into bookmarks(_id,title,url,folder,parent,position) values ( " + str ( 30 + i ) + " , \" Bookmark " + str ( i ) + " \" , \" http://www.bookmark " + str ( i ) + " .com \" ,0,1, " + str ( 100 + i ) + " ); ' " ]
else :
cmd = [ ' execsu ' , ' sqlite3 ' , " /data/data/com.android.browser/databases/browser.db ' insert into bookmarks(title,url,bookmark) values ( \" Bookmark " + str ( i ) + " \" , \" http://www.bookmark " + str ( i ) + " .com \" ,1); ' " ]
if ( options . dm_trans == " sut " ) :
dm . _runCmds ( [ { " cmd " : " " . join ( cmd ) } ] )
2012-01-07 15:41:08 -08:00
try :
2013-09-27 14:04:16 -07:00
screenShotDir = " /mnt/sdcard/Robotium-Screenshots "
dm . removeDir ( screenShotDir )
2012-06-13 11:20:43 -07:00
dm . recordLogcat ( )
2012-11-20 07:24:28 -08:00
result = mochitest . runTests ( options )
2012-11-21 10:57:11 -08:00
if result != 0 :
2013-09-05 09:14:54 -07:00
log . error ( " runTests() exited with code %s " , result )
2013-01-10 01:24:58 -08:00
log_result = mochitest . addLogData ( )
if result != 0 or log_result != 0 :
2013-06-24 17:15:40 -07:00
mochitest . printDeviceInfo ( printLogcat = True )
2013-09-27 14:04:16 -07:00
mochitest . printScreenshots ( screenShotDir )
2012-11-20 07:24:28 -08:00
# Ensure earlier failures aren't overwritten by success on this run
if retVal is None or retVal == 0 :
retVal = result
2012-01-07 15:41:08 -08:00
except :
2013-09-05 09:14:54 -07:00
log . error ( " Automation Error: Exception caught while running tests " )
2012-11-05 05:03:55 -08:00
traceback . print_exc ( )
2014-03-14 11:25:41 -07:00
mochitest . stopServers ( )
2012-01-11 05:50:10 -08:00
try :
2014-06-03 08:19:28 -07:00
mochitest . cleanup ( options )
2012-10-24 10:34:33 -07:00
except devicemanager . DMError :
# device error cleaning up... oh well!
2012-01-11 05:50:10 -08:00
pass
2012-10-24 10:34:33 -07:00
retVal = 1
break
2013-03-18 02:15:17 -07:00
finally :
# Clean-up added bookmarks
if test [ ' name ' ] == " testImportFromAndroid " :
if ( " pandaboard " in devOS ) :
cmd_del = [ ' execsu ' , ' sqlite3 ' , " /data/data/com.android.browser/databases/browser2.db \' delete from bookmarks where _id > 14; \' " ]
else :
cmd_del = [ ' execsu ' , ' sqlite3 ' , " /data/data/com.android.browser/databases/browser.db \' delete from bookmarks where _id > 14; \' " ]
if ( options . dm_trans == " sut " ) :
dm . _runCmds ( [ { " cmd " : " " . join ( cmd_del ) } ] )
2012-01-24 06:46:34 -08:00
if retVal is None :
2013-09-05 09:14:54 -07:00
log . warn ( " No tests run. Did you pass an invalid TEST_PATH? " )
2012-02-09 05:49:00 -08:00
retVal = 1
2012-11-21 10:53:48 -08:00
else :
2012-10-24 10:34:33 -07:00
# if we didn't have some kind of error running the tests, make
# sure the tests actually passed
2013-01-04 10:42:22 -08:00
print " INFO | runtests.py | Test summary: start. "
2012-11-21 10:53:48 -08:00
overallResult = mochitest . printLog ( )
2013-01-04 10:42:22 -08:00
print " INFO | runtests.py | Test summary: end. "
2012-11-21 10:53:48 -08:00
if retVal == 0 :
retVal = overallResult
2011-12-31 07:03:36 -08:00
else :
2012-01-11 05:50:10 -08:00
try :
2012-10-24 10:34:33 -07:00
dm . recordLogcat ( )
retVal = mochitest . runTests ( options )
2012-01-11 05:50:10 -08:00
except :
2013-09-05 09:14:54 -07:00
log . error ( " Automation Error: Exception caught while running tests " )
2012-11-05 05:03:55 -08:00
traceback . print_exc ( )
2014-03-14 11:25:41 -07:00
mochitest . stopServers ( )
2012-10-24 10:34:33 -07:00
try :
2014-06-03 08:19:28 -07:00
mochitest . cleanup ( options )
2012-10-24 10:34:33 -07:00
except devicemanager . DMError :
# device error cleaning up... oh well!
pass
retVal = 1
2013-06-24 17:15:40 -07:00
mochitest . printDeviceInfo ( printLogcat = True )
2012-01-07 15:41:08 -08:00
2011-09-26 04:41:19 -07:00
sys . exit ( retVal )
2012-12-04 07:54:37 -08:00
2010-02-25 11:10:39 -08:00
if __name__ == " __main__ " :
2010-06-24 02:32:01 -07:00
main ( )