# 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/.importConfigParserimportosimportsysimporttempfileimporttraceback# We need to know our current directory so that we can serve our test files from it.here=os.path.abspath(os.path.dirname(__file__))fromautomationimportAutomationfromb2gautomationimportB2GRemoteAutomationfromb2g_desktopimportrun_desktop_reftestsfromrunreftestimportRefTestfromrunreftestimportReftestOptionsfromremotereftestimportReftestServerfrommozdeviceimportDeviceManagerADB,DMErrorfrommarionetteimportMarionetteclassB2GOptions(ReftestOptions):def__init__(self,automation=None,**kwargs):defaults={}ifnotautomation:automation=B2GRemoteAutomation(None,"fennec",context_chrome=True)ReftestOptions.__init__(self,automation)self.add_option("--b2gpath",action="store",type="string",dest="b2gPath",help="path to B2G repo or qemu dir")defaults["b2gPath"]=Noneself.add_option("--marionette",action="store",type="string",dest="marionette",help="host:port to use when connecting to Marionette")defaults["marionette"]=Noneself.add_option("--emulator",action="store",type="string",dest="emulator",help="Architecture of emulator to use: x86 or arm")defaults["emulator"]=Noneself.add_option("--emulator-res",action="store",type="string",dest="emulator_res",help="Emulator resolution of the format '<width>x<height>'")defaults["emulator_res"]=Noneself.add_option("--no-window",action="store_true",dest="noWindow",help="Pass --no-window to the emulator")defaults["noWindow"]=Falseself.add_option("--adbpath",action="store",type="string",dest="adbPath",help="path to adb")defaults["adbPath"]="adb"self.add_option("--deviceIP",action="store",type="string",dest="deviceIP",help="ip address of remote device to test")defaults["deviceIP"]=Noneself.add_option("--devicePort",action="store",type="string",dest="devicePort",help="port of remote device to test")defaults["devicePort"]=20701self.add_option("--remote-logfile",action="store",type="string",dest="remoteLogFile",help="Name of log file on the device relative to the device root. PLEASE ONLY USE A FILENAME.")defaults["remoteLogFile"]=Noneself.add_option("--remote-webserver",action="store",type="string",dest="remoteWebServer",help="ip address where the remote web server is hosted at")defaults["remoteWebServer"]=Noneself.add_option("--http-port",action="store",type="string",dest="httpPort",help="ip address where the remote web server is hosted at")defaults["httpPort"]=automation.DEFAULT_HTTP_PORTself.add_option("--ssl-port",action="store",type="string",dest="sslPort",help="ip address where the remote web server is hosted at")defaults["sslPort"]=automation.DEFAULT_SSL_PORTself.add_option("--pidfile",action="store",type="string",dest="pidFile",help="name of the pidfile to generate")defaults["pidFile"]=""self.add_option("--gecko-path",action="store",type="string",dest="geckoPath",help="the path to a gecko distribution that should ""be installed on the emulator prior to test")defaults["geckoPath"]=Noneself.add_option("--logcat-dir",action="store",type="string",dest="logcat_dir",help="directory to store logcat dump files")defaults["logcat_dir"]=Noneself.add_option('--busybox',action='store',type='string',dest='busybox',help="Path to busybox binary to install on device")defaults['busybox']=Noneself.add_option("--httpd-path",action="store",type="string",dest="httpdPath",help="path to the httpd.js file")defaults["httpdPath"]=Noneself.add_option("--profile",action="store",type="string",dest="profile",help="for desktop testing, the path to the ""gaia profile to use")defaults["profile"]=Noneself.add_option("--desktop",action="store_true",dest="desktop",help="Run the tests on a B2G desktop build")defaults["desktop"]=Falsedefaults["remoteTestRoot"]="/data/local/tests"defaults["logFile"]="reftest.log"defaults["autorun"]=Truedefaults["closeWhenDone"]=Truedefaults["testPath"]=""defaults["runTestsInParallel"]=Falseself.set_defaults(**defaults)defverifyRemoteOptions(self,options):ifoptions.runTestsInParallel:self.error("Cannot run parallel tests here")ifnotoptions.remoteTestRoot:options.remoteTestRoot=self.automation._devicemanager.getDeviceRoot()+"/reftest"options.remoteProfile=options.remoteTestRoot+"/profile"productRoot=options.remoteTestRoot+"/"+self.automation._productifoptions.utilityPath==self.automation.DIST_BIN:options.utilityPath=productRoot+"/bin"ifoptions.remoteWebServer==None:ifos.name!="nt":options.remoteWebServer=self.automation.getLanIp()else:print"ERROR: you must specify a --remote-webserver=<ip address>\n"returnNoneoptions.webServer=options.remoteWebServerifoptions.geckoPathandnotoptions.emulator:self.error("You must specify --emulator if you specify --gecko-path")ifoptions.logcat_dirandnotoptions.emulator:self.error("You must specify --emulator if you specify --logcat-dir")#if not options.emulator and not options.deviceIP:# print "ERROR: you must provide a device IP"# return Noneifoptions.remoteLogFile==None:options.remoteLogFile="reftest.log"options.localLogName=options.remoteLogFileoptions.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 thatoptions.localLogName=options.logFileoptions.logFile=options.remoteLogFile# Only reset the xrePath if it wasn't providedifoptions.xrePath==None:options.xrePath=options.utilityPathoptions.xrePath=os.path.abspath(options.xrePath)ifoptions.pidFile!="":f=open(options.pidFile,'w')f.write("%s"%os.getpid())f.close()# 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 from the xre bundle, as required in bug 882932.ifnotoptions.httpdPath:options.httpdPath=os.path.join(options.xrePath,"components")returnoptionsclassProfileConfigParser(ConfigParser.RawConfigParser):"""Subclass of RawConfigParser that outputs .ini files in the exact format expected for profiles.ini, which is slightly different than the default format. """defoptionxform(self,optionstr):returnoptionstrdefwrite(self,fp):ifself._defaults:fp.write("[%s]\n"%ConfigParser.DEFAULTSECT)for(key,value)inself._defaults.items():fp.write("%s=%s\n"%(key,str(value).replace('\n','\n\t')))fp.write("\n")forsectioninself._sections:fp.write("[%s]\n"%section)for(key,value)inself._sections[section].items():ifkey=="__name__":continueif(valueisnotNone)or(self._optcre==self.OPTCRE):key="=".join((key,str(value).replace('\n','\n\t')))fp.write("%s\n"%(key))fp.write("\n")classB2GRemoteReftest(RefTest):_devicemanager=NonelocalProfile=NoneremoteApp=''profile=Nonedef__init__(self,automation,devicemanager,options,scriptDir):RefTest.__init__(self,automation)self._devicemanager=devicemanagerself.runSSLTunnel=Falseself.remoteTestRoot=options.remoteTestRootself.remoteProfile=options.remoteProfileself.automation.setRemoteProfile(self.remoteProfile)self.localLogName=options.localLogNameself.remoteLogFile=options.remoteLogFileself.bundlesDir='/system/b2g/distribution/bundles'self.userJS='/data/local/user.js'self.remoteMozillaPath='/data/b2g/mozilla'self.remoteProfilesIniPath=os.path.join(self.remoteMozillaPath,'profiles.ini')self.originalProfilesIni=Noneself.scriptDir=scriptDirself.SERVER_STARTUP_TIMEOUT=90ifself.automation.IS_DEBUG_BUILD:self.SERVER_STARTUP_TIMEOUT=180defcleanup(self,profileDir):# Pull results back from deviceif(self.remoteLogFile):try:self._devicemanager.getFile(self.remoteLogFile,self.localLogName)except:print"ERROR: We were not able to retrieve the info from %s"%self.remoteLogFilesys.exit(5)# Delete any bundled extensionsifprofileDir:extensionDir=os.path.join(profileDir,'extensions','staged')forfilenameinos.listdir(extensionDir):try:self._devicemanager._checkCmd(['shell','rm','-rf',os.path.join(self.bundlesDir,filename)])exceptDMError:pass# Restore the original profiles.ini.ifself.originalProfilesIni:try:ifnotself.automation._is_emulator:self.restoreProfilesIni()os.remove(self.originalProfilesIni)except:passifnotself.automation._is_emulator:self._devicemanager.removeFile(self.remoteLogFile)self._devicemanager.removeDir(self.remoteProfile)self._devicemanager.removeDir(self.remoteTestRoot)# Restore the original user.js.self._devicemanager._checkCmd(['shell','rm','-f',self.userJS])self._devicemanager._checkCmd(['shell','dd','if=%s.orig'%self.userJS,'of=%s'%self.userJS])# We've restored the original profile, so reboot the device so that# it gets picked up.self.automation.rebootDevice()RefTest.cleanup(self,profileDir)ifgetattr(self,'pidFile','')!='':try:os.remove(self.pidFile)os.remove(self.pidFile+".xpcshell.pid")except:print"Warning: cleaning up pidfile '%s' was unsuccessful from the test harness"%self.pidFiledeffindPath(self,paths,filename=None):forpathinpaths:p=pathiffilename:p=os.path.join(p,filename)ifos.path.exists(self.getFullPath(p)):returnpathreturnNonedefstartWebServer(self,options):""" Create the webserver on the host and start it up """remoteXrePath=options.xrePathremoteProfilePath=self.remoteProfileremoteUtilityPath=options.utilityPathlocalAutomation=Automation()localAutomation.IS_WIN32=FalselocalAutomation.IS_LINUX=FalselocalAutomation.IS_MAC=FalselocalAutomation.UNIXISH=Falsehostos=sys.platformifhostosin['mac','darwin']:localAutomation.IS_MAC=Trueelifhostosin['linux','linux2']:localAutomation.IS_LINUX=TruelocalAutomation.UNIXISH=Trueelifhostosin['win32','win64']:localAutomation.BIN_SUFFIX=".exe"localAutomation.IS_WIN32=Truepaths=[options.xrePath,localAutomation.DIST_BIN,self.automation._product,os.path.join('..',self.automation._product)]options.xrePath=self.findPath(paths)ifoptions.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"if(options.utilityPath):paths.insert(0,options.utilityPath)options.utilityPath=self.findPath(paths,xpcshell)ifoptions.utilityPath==None:print"ERROR: unable to find utility path for %s, please specify with --utility-path"%(os.name)sys.exit(1)xpcshell=os.path.join(options.utilityPath,xpcshell)ifself.automation.elf_arm(xpcshell):raiseException('xpcshell at %s is an ARM binary; please use ''the --utility-path argument to specify the path ''to a desktop version.'%xpcshell)options.serverProfilePath=tempfile.mkdtemp()self.server=ReftestServer(localAutomation,options,self.scriptDir)retVal=self.server.start()ifretVal:returnretValif(options.pidFile!=""):f=open(options.pidFile+".xpcshell.pid",'w')f.write("%s"%self.server._process.pid)f.close()retVal=self.server.ensureReady(self.SERVER_STARTUP_TIMEOUT)ifretVal:returnretValoptions.xrePath=remoteXrePathoptions.utilityPath=remoteUtilityPathoptions.profilePath=remoteProfilePathreturn0defstopWebServer(self,options):ifhasattr(self,'server'):self.server.stop()defrestoreProfilesIni(self):# restore profiles.ini on the device to its previous stateifnotself.originalProfilesIniornotos.access(self.originalProfilesIni,os.F_OK):raiseDMError('Unable to install original profiles.ini; file not found: %s',self.originalProfilesIni)self._devicemanager.pushFile(self.originalProfilesIni,self.remoteProfilesIniPath)defupdateProfilesIni(self,profilePath):# update profiles.ini on the device to point to the test profileself.originalProfilesIni=tempfile.mktemp()self._devicemanager.getFile(self.remoteProfilesIniPath,self.originalProfilesIni)config=ProfileConfigParser()config.read(self.originalProfilesIni)forsectioninconfig.sections():if'Profile'insection:config.set(section,'IsRelative',0)config.set(section,'Path',profilePath)newProfilesIni=tempfile.mktemp()withopen(newProfilesIni,'wb')asconfigfile:config.write(configfile)self._devicemanager.pushFile(newProfilesIni,self.remoteProfilesIniPath)try:os.remove(newProfilesIni)except:passdefcreateReftestProfile(self,options,reftestlist):profile=RefTest.createReftestProfile(self,options,reftestlist,server=options.remoteWebServer,special_powers=False)profileDir=profile.profileprefs={}# Turn off the locale picker screenprefs["browser.firstrun.show.localepicker"]=Falseprefs["browser.homescreenURL"]="app://test-container.gaiamobile.org/index.html"prefs["browser.manifestURL"]="app://test-container.gaiamobile.org/manifest.webapp"prefs["browser.tabs.remote"]=Falseprefs["dom.ipc.tabs.disabled"]=Falseprefs["dom.mozBrowserFramesEnabled"]=Trueprefs["font.size.inflation.emPerLine"]=0prefs["font.size.inflation.minTwips"]=0prefs["network.dns.localDomains"]="app://test-container.gaiamobile.org"prefs["reftest.browser.iframe.enabled"]=Falseprefs["reftest.remote"]=Trueprefs["reftest.uri"]="%s"%reftestlist# Set a future policy version to avoid the telemetry prompt.prefs["toolkit.telemetry.prompted"]=999prefs["toolkit.telemetry.notifiedOptOut"]=999# Set the extra prefs.profile.set_preferences(prefs)# Copy the profile to the device.self._devicemanager.removeDir(self.remoteProfile)try:self._devicemanager.pushDir(profileDir,self.remoteProfile)exceptDMError:print"Automation Error: Unable to copy profile to device."raise# Copy the extensions to the B2G bundles dir.extensionDir=os.path.join(profileDir,'extensions','staged')# need to write to read-only dirself._devicemanager._checkCmd(['remount'])forfilenameinos.listdir(extensionDir):self._devicemanager._checkCmd(['shell','rm','-rf',os.path.join(self.bundlesDir,filename)])try:self._devicemanager.pushDir(extensionDir,self.bundlesDir)exceptDMError:print"Automation Error: Unable to copy extensions to device."raise# In B2G, user.js is always read from /data/local, not the profile# directory. Backup the original user.js first so we can restore it.self._devicemanager._checkCmd(['shell','rm','-f','%s.orig'%self.userJS])self._devicemanager._checkCmd(['shell','dd','if=%s'%self.userJS,'of=%s.orig'%self.userJS])self._devicemanager.pushFile(os.path.join(profileDir,"user.js"),self.userJS)self.updateProfilesIni(self.remoteProfile)options.profilePath=self.remoteProfilereturnprofiledefcopyExtraFilesToProfile(self,options,profile):profileDir=profile.profileRefTest.copyExtraFilesToProfile(self,options,profile)try:self._devicemanager.pushDir(profileDir,options.remoteProfile)exceptDMError:print"Automation Error: Failed to copy extra files to device"raisedefgetManifestPath(self,path):returnpathdefrun_remote_reftests(parser,options,args):auto=B2GRemoteAutomation(None,"fennec",context_chrome=True)# create our Marionette instancekwargs={}ifoptions.emulator:kwargs['emulator']=options.emulatorauto.setEmulator(True)ifoptions.noWindow:kwargs['noWindow']=Trueifoptions.geckoPath:kwargs['gecko_path']=options.geckoPathifoptions.logcat_dir:kwargs['logcat_dir']=options.logcat_dirifoptions.busybox:kwargs['busybox']=options.busyboxifoptions.symbolsPath:kwargs['symbols_path']=options.symbolsPathifoptions.emulator_res:kwargs['emulator_res']=options.emulator_resifoptions.b2gPath:kwargs['homedir']=options.b2gPathifoptions.marionette:host,port=options.marionette.split(':')kwargs['host']=hostkwargs['port']=int(port)marionette=Marionette.getMarionetteOrExit(**kwargs)auto.marionette=marionetteifoptions.emulator:dm=marionette.emulator.dmelse:# create the DeviceManagerkwargs={'adbPath':options.adbPath,'deviceRoot':options.remoteTestRoot}ifoptions.deviceIP:kwargs.update({'host':options.deviceIP,'port':options.devicePort})dm=DeviagerADB(**kwargs)auto.setDeviceManager(dm)options=parser.verifyRemoteOptions(options)if(options==None):print"ERROR: Invalid options specified, use --help for a list of valid options"sys.exit(1)# TODO fix exceptionifnotoptions.ignoreWindowSize:parts=dm.getInfo('screen')['screen'][0].split()width=int(parts[0].split(':')[1])height=int(parts[1].split(':')[1])if(width<1366orheight<1050):print"ERROR: Invalid screen resolution %sx%s, please adjust to 1366x1050 or higher"%(width,height)return1auto.setProduct("b2g")auto.test_script=os.path.join(here,'b2g_start_script.js')auto.test_script_args=[options.remoteWebServer,options.httpPort]auto.logFinish="REFTEST TEST-START | Shutdown"reftest=B2GRemoteReftest(auto,dm,options,here)options=parser.verifyCommonOptions(options,reftest)logParent=os.path.dirname(options.remoteLogFile)dm.mkDir(logParent);auto.setRemoteLog(options.remoteLogFile)auto.setServerInfo(options.webServer,options.httpPort,options.sslPort)# Hack in a symbolic link for jsreftestos.system("ln -s %s%s"%(os.path.join('..','jsreftest'),os.path.join(here,'jsreftest')))# Dynamically build the reftest URL if possible, beware that args[0] should exist 'inside' the webrootmanifest=args[0]ifos.path.exists(os.path.join(here,args[0])):manifest="http://%s:%s/%s"%(options.remoteWebServer,options.httpPort,args[0])elifos.path.exists(args[0]):manifestPath=os.path.abspath(args[0]).split(here)[1].strip('/')manifest="http://%s:%s/%s"%(options.remoteWebServer,options.httpPort,manifestPath)else:print"ERROR: Could not find test manifest '%s'"%manifestreturn1# Start the webserverretVal=1try:retVal=reftest.startWebServer(options)ifretVal:returnretValprocName=options.app.split('/')[-1]if(dm.processExist(procName)):dm.killProcess(procName)cmdlineArgs=["-reftest",manifest]ifgetattr(options,'bootstrap',False):cmdlineArgs=[]retVal=reftest.runTests(manifest,options,cmdlineArgs)except:print"Automation Error: Exception caught while running tests"traceback.print_exc()reftest.stopWebServer(options)try:reftest.cleanup(None)except:passreturn1reftest.stopWebServer(options)returnretValdefmain(args=sys.argv[1:]):parser=B2GOptions()options,args=parser.parse_args(args)ifoptions.desktop:returnrun_desktop_reftests(parser,options,args)returnrun_remote_reftests(parser,options,args)if__name__=="__main__":sys.exit(main())