layout/tools/reftest/runreftestb2g.py

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 # This Source Code Form is subject to the terms of the Mozilla Public
     2 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3 # You can obtain one at http://mozilla.org/MPL/2.0/.
     5 import ConfigParser
     6 import os
     7 import sys
     8 import tempfile
     9 import traceback
    11 # We need to know our current directory so that we can serve our test files from it.
    12 here = os.path.abspath(os.path.dirname(__file__))
    14 from automation import Automation
    15 from b2gautomation import B2GRemoteAutomation
    16 from b2g_desktop import run_desktop_reftests
    17 from runreftest import RefTest
    18 from runreftest import ReftestOptions
    19 from remotereftest import ReftestServer
    21 from mozdevice import DeviceManagerADB, DMError
    22 from marionette import Marionette
    23 import moznetwork
    25 class B2GOptions(ReftestOptions):
    27     def __init__(self, automation=None, **kwargs):
    28         defaults = {}
    29         if not automation:
    30             automation = B2GRemoteAutomation(None, "fennec", context_chrome=True)
    32         ReftestOptions.__init__(self, automation)
    34         self.add_option("--browser-arg", action="store",
    35                     type = "string", dest = "browser_arg",
    36                     help = "Optional command-line arg to pass to the browser")
    37         defaults["browser_arg"] = None
    39         self.add_option("--b2gpath", action="store",
    40                     type = "string", dest = "b2gPath",
    41                     help = "path to B2G repo or qemu dir")
    42         defaults["b2gPath"] = None
    44         self.add_option("--marionette", action="store",
    45                     type = "string", dest = "marionette",
    46                     help = "host:port to use when connecting to Marionette")
    47         defaults["marionette"] = None
    49         self.add_option("--emulator", action="store",
    50                     type="string", dest = "emulator",
    51                     help = "Architecture of emulator to use: x86 or arm")
    52         defaults["emulator"] = None
    53         self.add_option("--emulator-res", action="store",
    54                     type="string", dest = "emulator_res",
    55                     help = "Emulator resolution of the format '<width>x<height>'")
    56         defaults["emulator_res"] = None
    58         self.add_option("--no-window", action="store_true",
    59                     dest = "noWindow",
    60                     help = "Pass --no-window to the emulator")
    61         defaults["noWindow"] = False
    63         self.add_option("--adbpath", action="store",
    64                     type = "string", dest = "adbPath",
    65                     help = "path to adb")
    66         defaults["adbPath"] = "adb"
    68         self.add_option("--deviceIP", action="store",
    69                     type = "string", dest = "deviceIP",
    70                     help = "ip address of remote device to test")
    71         defaults["deviceIP"] = None
    73         self.add_option("--devicePort", action="store",
    74                     type = "string", dest = "devicePort",
    75                     help = "port of remote device to test")
    76         defaults["devicePort"] = 20701
    78         self.add_option("--remote-logfile", action="store",
    79                     type = "string", dest = "remoteLogFile",
    80                     help = "Name of log file on the device relative to the device root.  PLEASE ONLY USE A FILENAME.")
    81         defaults["remoteLogFile"] = None
    83         self.add_option("--remote-webserver", action = "store",
    84                     type = "string", dest = "remoteWebServer",
    85                     help = "ip address where the remote web server is hosted at")
    86         defaults["remoteWebServer"] = None
    88         self.add_option("--http-port", action = "store",
    89                     type = "string", dest = "httpPort",
    90                     help = "ip address where the remote web server is hosted at")
    91         defaults["httpPort"] = automation.DEFAULT_HTTP_PORT
    93         self.add_option("--ssl-port", action = "store",
    94                     type = "string", dest = "sslPort",
    95                     help = "ip address where the remote web server is hosted at")
    96         defaults["sslPort"] = automation.DEFAULT_SSL_PORT
    98         self.add_option("--pidfile", action = "store",
    99                     type = "string", dest = "pidFile",
   100                     help = "name of the pidfile to generate")
   101         defaults["pidFile"] = ""
   102         self.add_option("--gecko-path", action="store",
   103                         type="string", dest="geckoPath",
   104                         help="the path to a gecko distribution that should "
   105                         "be installed on the emulator prior to test")
   106         defaults["geckoPath"] = None
   107         self.add_option("--logcat-dir", action="store",
   108                         type="string", dest="logcat_dir",
   109                         help="directory to store logcat dump files")
   110         defaults["logcat_dir"] = None
   111         self.add_option('--busybox', action='store',
   112                         type='string', dest='busybox',
   113                         help="Path to busybox binary to install on device")
   114         defaults['busybox'] = None
   115         self.add_option("--httpd-path", action = "store",
   116                     type = "string", dest = "httpdPath",
   117                     help = "path to the httpd.js file")
   118         defaults["httpdPath"] = None
   119         self.add_option("--profile", action="store",
   120                     type="string", dest="profile",
   121                     help="for desktop testing, the path to the "
   122                          "gaia profile to use")
   123         defaults["profile"] = None
   124         self.add_option("--desktop", action="store_true",
   125                         dest="desktop",
   126                         help="Run the tests on a B2G desktop build")
   127         defaults["desktop"] = False
   128         defaults["remoteTestRoot"] = "/data/local/tests"
   129         defaults["logFile"] = "reftest.log"
   130         defaults["autorun"] = True
   131         defaults["closeWhenDone"] = True
   132         defaults["testPath"] = ""
   133         defaults["runTestsInParallel"] = False
   135         self.set_defaults(**defaults)
   137     def verifyRemoteOptions(self, options):
   138         if options.runTestsInParallel:
   139             self.error("Cannot run parallel tests here")
   141         if not options.remoteTestRoot:
   142             options.remoteTestRoot = self.automation._devicemanager.getDeviceRoot() + "/reftest"
   143         options.remoteProfile = options.remoteTestRoot + "/profile"
   145         productRoot = options.remoteTestRoot + "/" + self.automation._product
   146         if options.utilityPath == self.automation.DIST_BIN:
   147             options.utilityPath = productRoot + "/bin"
   149         if options.remoteWebServer == None:
   150             if os.name != "nt":
   151                 options.remoteWebServer = moznetwork.get_ip()
   152             else:
   153                 print "ERROR: you must specify a --remote-webserver=<ip address>\n"
   154                 return None
   156         options.webServer = options.remoteWebServer
   158         if options.geckoPath and not options.emulator:
   159             self.error("You must specify --emulator if you specify --gecko-path")
   161         if options.logcat_dir and not options.emulator:
   162             self.error("You must specify --emulator if you specify --logcat-dir")
   164         #if not options.emulator and not options.deviceIP:
   165         #    print "ERROR: you must provide a device IP"
   166         #    return None
   168         if options.remoteLogFile == None:
   169             options.remoteLogFile = "reftest.log"
   171         options.localLogName = options.remoteLogFile
   172         options.remoteLogFile = options.remoteTestRoot + '/' + options.remoteLogFile
   174         # Ensure that the options.logfile (which the base class uses) is set to
   175         # the remote setting when running remote. Also, if the user set the
   176         # log file name there, use that instead of reusing the remotelogfile as above.
   177         if (options.logFile):
   178             # If the user specified a local logfile name use that
   179             options.localLogName = options.logFile
   180         options.logFile = options.remoteLogFile
   182         # Only reset the xrePath if it wasn't provided
   183         if options.xrePath == None:
   184             options.xrePath = options.utilityPath
   185         options.xrePath = os.path.abspath(options.xrePath)
   187         if options.pidFile != "":
   188             f = open(options.pidFile, 'w')
   189             f.write("%s" % os.getpid())
   190             f.close()
   192         # httpd-path is specified by standard makefile targets and may be specified
   193         # on the command line to select a particular version of httpd.js. If not
   194         # specified, try to select the one from from the xre bundle, as required in bug 882932.
   195         if not options.httpdPath:
   196             options.httpdPath = os.path.join(options.xrePath, "components")
   198         return options
   201 class ProfileConfigParser(ConfigParser.RawConfigParser):
   202     """Subclass of RawConfigParser that outputs .ini files in the exact
   203        format expected for profiles.ini, which is slightly different
   204        than the default format.
   205     """
   207     def optionxform(self, optionstr):
   208         return optionstr
   210     def write(self, fp):
   211         if self._defaults:
   212             fp.write("[%s]\n" % ConfigParser.DEFAULTSECT)
   213             for (key, value) in self._defaults.items():
   214                 fp.write("%s=%s\n" % (key, str(value).replace('\n', '\n\t')))
   215             fp.write("\n")
   216         for section in self._sections:
   217             fp.write("[%s]\n" % section)
   218             for (key, value) in self._sections[section].items():
   219                 if key == "__name__":
   220                     continue
   221                 if (value is not None) or (self._optcre == self.OPTCRE):
   222                     key = "=".join((key, str(value).replace('\n', '\n\t')))
   223                 fp.write("%s\n" % (key))
   224             fp.write("\n")
   226 class B2GRemoteReftest(RefTest):
   228     _devicemanager = None
   229     localProfile = None
   230     remoteApp = ''
   231     profile = None
   233     def __init__(self, automation, devicemanager, options, scriptDir):
   234         RefTest.__init__(self, automation)
   235         self._devicemanager = devicemanager
   236         self.runSSLTunnel = False
   237         self.remoteTestRoot = options.remoteTestRoot
   238         self.remoteProfile = options.remoteProfile
   239         self.automation.setRemoteProfile(self.remoteProfile)
   240         self.localLogName = options.localLogName
   241         self.remoteLogFile = options.remoteLogFile
   242         self.bundlesDir = '/system/b2g/distribution/bundles'
   243         self.userJS = '/data/local/user.js'
   244         self.remoteMozillaPath = '/data/b2g/mozilla'
   245         self.remoteProfilesIniPath = os.path.join(self.remoteMozillaPath, 'profiles.ini')
   246         self.originalProfilesIni = None
   247         self.scriptDir = scriptDir
   248         self.SERVER_STARTUP_TIMEOUT = 90
   249         if self.automation.IS_DEBUG_BUILD:
   250             self.SERVER_STARTUP_TIMEOUT = 180
   252     def cleanup(self, profileDir):
   253         # Pull results back from device
   254         if (self.remoteLogFile):
   255             try:
   256                 self._devicemanager.getFile(self.remoteLogFile, self.localLogName)
   257             except:
   258                 print "ERROR: We were not able to retrieve the info from %s" % self.remoteLogFile
   259                 sys.exit(5)
   261         # Delete any bundled extensions
   262         if profileDir:
   263             extensionDir = os.path.join(profileDir, 'extensions', 'staged')
   264             for filename in os.listdir(extensionDir):
   265                 try:
   266                     self._devicemanager._checkCmd(['shell', 'rm', '-rf',
   267                                                      os.path.join(self.bundlesDir, filename)])
   268                 except DMError:
   269                     pass
   271         # Restore the original profiles.ini.
   272         if self.originalProfilesIni:
   273             try:
   274                 if not self.automation._is_emulator:
   275                     self.restoreProfilesIni()
   276                 os.remove(self.originalProfilesIni)
   277             except:
   278                 pass
   280         if not self.automation._is_emulator:
   281             self._devicemanager.removeFile(self.remoteLogFile)
   282             self._devicemanager.removeDir(self.remoteProfile)
   283             self._devicemanager.removeDir(self.remoteTestRoot)
   285             # Restore the original user.js.
   286             self._devicemanager._checkCmd(['shell', 'rm', '-f', self.userJS])
   287             self._devicemanager._checkCmd(['shell', 'dd', 'if=%s.orig' % self.userJS, 'of=%s' % self.userJS])
   289             # We've restored the original profile, so reboot the device so that
   290             # it gets picked up.
   291             self.automation.rebootDevice()
   293         RefTest.cleanup(self, profileDir)
   294         if getattr(self, 'pidFile', '') != '':
   295             try:
   296                 os.remove(self.pidFile)
   297                 os.remove(self.pidFile + ".xpcshell.pid")
   298             except:
   299                 print "Warning: cleaning up pidfile '%s' was unsuccessful from the test harness" % self.pidFile
   301     def findPath(self, paths, filename = None):
   302         for path in paths:
   303             p = path
   304             if filename:
   305                 p = os.path.join(p, filename)
   306             if os.path.exists(self.getFullPath(p)):
   307                 return path
   308         return None
   310     def startWebServer(self, options):
   311         """ Create the webserver on the host and start it up """
   312         remoteXrePath = options.xrePath
   313         remoteProfilePath = self.remoteProfile
   314         remoteUtilityPath = options.utilityPath
   315         localAutomation = Automation()
   316         localAutomation.IS_WIN32 = False
   317         localAutomation.IS_LINUX = False
   318         localAutomation.IS_MAC = False
   319         localAutomation.UNIXISH = False
   320         hostos = sys.platform
   321         if hostos in ['mac', 'darwin']:
   322             localAutomation.IS_MAC = True
   323         elif hostos in ['linux', 'linux2']:
   324             localAutomation.IS_LINUX = True
   325             localAutomation.UNIXISH = True
   326         elif hostos in ['win32', 'win64']:
   327             localAutomation.BIN_SUFFIX = ".exe"
   328             localAutomation.IS_WIN32 = True
   330         paths = [options.xrePath,
   331                  localAutomation.DIST_BIN,
   332                  self.automation._product,
   333                  os.path.join('..', self.automation._product)]
   334         options.xrePath = self.findPath(paths)
   335         if options.xrePath == None:
   336             print "ERROR: unable to find xulrunner path for %s, please specify with --xre-path" % (os.name)
   337             sys.exit(1)
   338         paths.append("bin")
   339         paths.append(os.path.join("..", "bin"))
   341         xpcshell = "xpcshell"
   342         if (os.name == "nt"):
   343             xpcshell += ".exe"
   345         if (options.utilityPath):
   346             paths.insert(0, options.utilityPath)
   347         options.utilityPath = self.findPath(paths, xpcshell)
   348         if options.utilityPath == None:
   349             print "ERROR: unable to find utility path for %s, please specify with --utility-path" % (os.name)
   350             sys.exit(1)
   352         xpcshell = os.path.join(options.utilityPath, xpcshell)
   353         if self.automation.elf_arm(xpcshell):
   354             raise Exception('xpcshell at %s is an ARM binary; please use '
   355                             'the --utility-path argument to specify the path '
   356                             'to a desktop version.' % xpcshell)
   358         options.serverProfilePath = tempfile.mkdtemp()
   359         self.server = ReftestServer(localAutomation, options, self.scriptDir)
   360         retVal = self.server.start()
   361         if retVal:
   362             return retVal
   364         if (options.pidFile != ""):
   365             f = open(options.pidFile + ".xpcshell.pid", 'w')
   366             f.write("%s" % self.server._process.pid)
   367             f.close()
   369         retVal = self.server.ensureReady(self.SERVER_STARTUP_TIMEOUT)
   370         if retVal:
   371             return retVal
   373         options.xrePath = remoteXrePath
   374         options.utilityPath = remoteUtilityPath
   375         options.profilePath = remoteProfilePath
   376         return 0
   378     def stopWebServer(self, options):
   379         if hasattr(self, 'server'):
   380             self.server.stop()
   382     def restoreProfilesIni(self):
   383         # restore profiles.ini on the device to its previous state
   384         if not self.originalProfilesIni or not os.access(self.originalProfilesIni, os.F_OK):
   385             raise DMError('Unable to install original profiles.ini; file not found: %s',
   386                           self.originalProfilesIni)
   388         self._devicemanager.pushFile(self.originalProfilesIni, self.remoteProfilesIniPath)
   390     def updateProfilesIni(self, profilePath):
   391         # update profiles.ini on the device to point to the test profile
   392         self.originalProfilesIni = tempfile.mktemp()
   393         self._devicemanager.getFile(self.remoteProfilesIniPath, self.originalProfilesIni)
   395         config = ProfileConfigParser()
   396         config.read(self.originalProfilesIni)
   397         for section in config.sections():
   398             if 'Profile' in section:
   399                 config.set(section, 'IsRelative', 0)
   400                 config.set(section, 'Path', profilePath)
   402         newProfilesIni = tempfile.mktemp()
   403         with open(newProfilesIni, 'wb') as configfile:
   404             config.write(configfile)
   406         self._devicemanager.pushFile(newProfilesIni, self.remoteProfilesIniPath)
   407         try:
   408             os.remove(newProfilesIni)
   409         except:
   410             pass
   413     def createReftestProfile(self, options, reftestlist):
   414         profile = RefTest.createReftestProfile(self, options, reftestlist,
   415                                                server=options.remoteWebServer,
   416                                                special_powers=False)
   417         profileDir = profile.profile
   419         prefs = {}
   420         # Turn off the locale picker screen
   421         prefs["browser.firstrun.show.localepicker"] = False
   422         prefs["browser.homescreenURL"] = "app://test-container.gaiamobile.org/index.html"
   423         prefs["browser.manifestURL"] = "app://test-container.gaiamobile.org/manifest.webapp"
   424         prefs["browser.tabs.remote"] = False
   425         prefs["dom.ipc.tabs.disabled"] = False
   426         prefs["dom.mozBrowserFramesEnabled"] = True
   427         prefs["font.size.inflation.emPerLine"] = 0
   428         prefs["font.size.inflation.minTwips"] = 0
   429         prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org"
   430         prefs["reftest.browser.iframe.enabled"] = False
   431         prefs["reftest.remote"] = True
   432         prefs["reftest.uri"] = "%s" % reftestlist
   433         # Set a future policy version to avoid the telemetry prompt.
   434         prefs["toolkit.telemetry.prompted"] = 999
   435         prefs["toolkit.telemetry.notifiedOptOut"] = 999
   437         # Set the extra prefs.
   438         profile.set_preferences(prefs)
   440         # Copy the profile to the device.
   441         self._devicemanager.removeDir(self.remoteProfile)
   442         try:
   443             self._devicemanager.pushDir(profileDir, self.remoteProfile)
   444         except DMError:
   445             print "Automation Error: Unable to copy profile to device."
   446             raise
   448         # Copy the extensions to the B2G bundles dir.
   449         extensionDir = os.path.join(profileDir, 'extensions', 'staged')
   450         # need to write to read-only dir
   451         self._devicemanager._checkCmd(['remount'])
   452         for filename in os.listdir(extensionDir):
   453             self._devicemanager._checkCmd(['shell', 'rm', '-rf',
   454                                              os.path.join(self.bundlesDir, filename)])
   455         try:
   456             self._devicemanager.pushDir(extensionDir, self.bundlesDir)
   457         except DMError:
   458             print "Automation Error: Unable to copy extensions to device."
   459             raise
   461         # In B2G, user.js is always read from /data/local, not the profile
   462         # directory.  Backup the original user.js first so we can restore it.
   463         self._devicemanager._checkCmd(['shell', 'rm', '-f', '%s.orig' % self.userJS])
   464         self._devicemanager._checkCmd(['shell', 'dd', 'if=%s' % self.userJS, 'of=%s.orig' % self.userJS])
   465         self._devicemanager.pushFile(os.path.join(profileDir, "user.js"), self.userJS)
   467         self.updateProfilesIni(self.remoteProfile)
   469         options.profilePath = self.remoteProfile
   470         return profile
   472     def copyExtraFilesToProfile(self, options, profile):
   473         profileDir = profile.profile
   474         RefTest.copyExtraFilesToProfile(self, options, profile)
   475         try:
   476             self._devicemanager.pushDir(profileDir, options.remoteProfile)
   477         except DMError:
   478             print "Automation Error: Failed to copy extra files to device"
   479             raise
   481     def getManifestPath(self, path):
   482         return path
   485 def run_remote_reftests(parser, options, args):
   486     auto = B2GRemoteAutomation(None, "fennec", context_chrome=True)
   488     # create our Marionette instance
   489     kwargs = {}
   490     if options.emulator:
   491         kwargs['emulator'] = options.emulator
   492         auto.setEmulator(True)
   493         if options.noWindow:
   494             kwargs['noWindow'] = True
   495         if options.geckoPath:
   496             kwargs['gecko_path'] = options.geckoPath
   497         if options.logcat_dir:
   498             kwargs['logcat_dir'] = options.logcat_dir
   499         if options.busybox:
   500             kwargs['busybox'] = options.busybox
   501         if options.symbolsPath:
   502             kwargs['symbols_path'] = options.symbolsPath
   503     if options.emulator_res:
   504         kwargs['emulator_res'] = options.emulator_res
   505     if options.b2gPath:
   506         kwargs['homedir'] = options.b2gPath
   507     if options.marionette:
   508         host,port = options.marionette.split(':')
   509         kwargs['host'] = host
   510         kwargs['port'] = int(port)
   511     marionette = Marionette.getMarionetteOrExit(**kwargs)
   512     auto.marionette = marionette
   514     if options.emulator:
   515         dm = marionette.emulator.dm
   516     else:
   517         # create the DeviceManager
   518         kwargs = {'adbPath': options.adbPath,
   519                   'deviceRoot': options.remoteTestRoot}
   520         if options.deviceIP:
   521             kwargs.update({'host': options.deviceIP,
   522                            'port': options.devicePort})
   523         dm = DeviagerADB(**kwargs)
   524     auto.setDeviceManager(dm)
   526     options = parser.verifyRemoteOptions(options)
   528     if (options == None):
   529         print "ERROR: Invalid options specified, use --help for a list of valid options"
   530         sys.exit(1)
   532     # TODO fix exception
   533     if not options.ignoreWindowSize:
   534         parts = dm.getInfo('screen')['screen'][0].split()
   535         width = int(parts[0].split(':')[1])
   536         height = int(parts[1].split(':')[1])
   537         if (width < 1366 or height < 1050):
   538             print "ERROR: Invalid screen resolution %sx%s, please adjust to 1366x1050 or higher" % (width, height)
   539             return 1
   541     auto.setProduct("b2g")
   542     auto.test_script = os.path.join(here, 'b2g_start_script.js')
   543     auto.test_script_args = [options.remoteWebServer, options.httpPort]
   544     auto.logFinish = "REFTEST TEST-START | Shutdown"
   546     reftest = B2GRemoteReftest(auto, dm, options, here)
   547     options = parser.verifyCommonOptions(options, reftest)
   549     logParent = os.path.dirname(options.remoteLogFile)
   550     dm.mkDir(logParent);
   551     auto.setRemoteLog(options.remoteLogFile)
   552     auto.setServerInfo(options.webServer, options.httpPort, options.sslPort)
   554     # Hack in a symbolic link for jsreftest
   555     os.system("ln -s %s %s" % (os.path.join('..', 'jsreftest'), os.path.join(here, 'jsreftest')))
   557     # Dynamically build the reftest URL if possible, beware that args[0] should exist 'inside' the webroot
   558     manifest = args[0]
   559     if os.path.exists(os.path.join(here, args[0])):
   560         manifest = "http://%s:%s/%s" % (options.remoteWebServer, options.httpPort, args[0])
   561     elif os.path.exists(args[0]):
   562         manifestPath = os.path.abspath(args[0]).split(here)[1].strip('/')
   563         manifest = "http://%s:%s/%s" % (options.remoteWebServer, options.httpPort, manifestPath)
   564     else:
   565         print "ERROR: Could not find test manifest '%s'" % manifest
   566         return 1
   568     # Start the webserver
   569     retVal = 1
   570     try:
   571         retVal = reftest.startWebServer(options)
   572         if retVal:
   573             return retVal
   574         procName = options.app.split('/')[-1]
   575         if (dm.processExist(procName)):
   576             dm.killProcess(procName)
   578         cmdlineArgs = ["-reftest", manifest]
   579         if getattr(options, 'bootstrap', False):
   580             cmdlineArgs = []
   582         retVal = reftest.runTests(manifest, options, cmdlineArgs)
   583     except:
   584         print "Automation Error: Exception caught while running tests"
   585         traceback.print_exc()
   586         reftest.stopWebServer(options)
   587         try:
   588             reftest.cleanup(None)
   589         except:
   590             pass
   591         return 1
   593     reftest.stopWebServer(options)
   594     return retVal
   596 def main(args=sys.argv[1:]):
   597     parser = B2GOptions()
   598     options, args = parser.parse_args(args)
   600     if options.desktop:
   601         return run_desktop_reftests(parser, options, args)
   602     return run_remote_reftests(parser, options, args)
   605 if __name__ == "__main__":
   606     sys.exit(main())

mercurial