testing/mochitest/runtestsb2g.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/mochitest/runtestsb2g.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,381 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 +# You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +import json
     1.9 +import os
    1.10 +import posixpath
    1.11 +import shutil
    1.12 +import sys
    1.13 +import tempfile
    1.14 +import threading
    1.15 +import time
    1.16 +import traceback
    1.17 +
    1.18 +here = os.path.abspath(os.path.dirname(__file__))
    1.19 +sys.path.insert(0, here)
    1.20 +
    1.21 +from runtests import Mochitest
    1.22 +from runtests import MochitestUtilsMixin
    1.23 +from runtests import MochitestOptions
    1.24 +from runtests import MochitestServer
    1.25 +from mochitest_options import B2GOptions, MochitestOptions
    1.26 +
    1.27 +from marionette import Marionette
    1.28 +
    1.29 +from mozdevice import DeviceManagerADB
    1.30 +from mozprofile import Profile, Preferences
    1.31 +from mozrunner import B2GRunner
    1.32 +import mozlog
    1.33 +import mozinfo
    1.34 +import moznetwork
    1.35 +
    1.36 +log = mozlog.getLogger('Mochitest')
    1.37 +
    1.38 +class B2GMochitest(MochitestUtilsMixin):
    1.39 +    def __init__(self, marionette,
    1.40 +                       out_of_process=True,
    1.41 +                       profile_data_dir=None,
    1.42 +                       locations=os.path.join(here, 'server-locations.txt')):
    1.43 +        super(B2GMochitest, self).__init__()
    1.44 +        self.marionette = marionette
    1.45 +        self.out_of_process = out_of_process
    1.46 +        self.locations_file = locations
    1.47 +        self.preferences = []
    1.48 +        self.webapps = None
    1.49 +        self.test_script = os.path.join(here, 'b2g_start_script.js')
    1.50 +        self.test_script_args = [self.out_of_process]
    1.51 +        self.product = 'b2g'
    1.52 +
    1.53 +        if profile_data_dir:
    1.54 +            self.preferences = [os.path.join(profile_data_dir, f)
    1.55 +                                 for f in os.listdir(profile_data_dir) if f.startswith('pref')]
    1.56 +            self.webapps = [os.path.join(profile_data_dir, f)
    1.57 +                             for f in os.listdir(profile_data_dir) if f.startswith('webapp')]
    1.58 +
    1.59 +        # mozinfo is populated by the parent class
    1.60 +        if mozinfo.info['debug']:
    1.61 +            self.SERVER_STARTUP_TIMEOUT = 180
    1.62 +        else:
    1.63 +            self.SERVER_STARTUP_TIMEOUT = 90
    1.64 +
    1.65 +    def setup_common_options(self, options):
    1.66 +        test_url = self.buildTestPath(options)
    1.67 +        if len(self.urlOpts) > 0:
    1.68 +            test_url += "?" + "&".join(self.urlOpts)
    1.69 +        self.test_script_args.append(test_url)
    1.70 +
    1.71 +    def buildTestPath(self, options):
    1.72 +        # Skip over the manifest building that happens on desktop.
    1.73 +        return self.buildTestURL(options)
    1.74 +
    1.75 +    def build_profile(self, options):
    1.76 +        # preferences
    1.77 +        prefs = {}
    1.78 +        for path in self.preferences:
    1.79 +            prefs.update(Preferences.read_prefs(path))
    1.80 +
    1.81 +        for v in options.extraPrefs:
    1.82 +            thispref = v.split("=", 1)
    1.83 +            if len(thispref) < 2:
    1.84 +                print "Error: syntax error in --setpref=" + v
    1.85 +                sys.exit(1)
    1.86 +            prefs[thispref[0]] = thispref[1]
    1.87 +
    1.88 +        # interpolate the preferences
    1.89 +        interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort),
    1.90 +                          "OOP": "true" if self.out_of_process else "false" }
    1.91 +        prefs = json.loads(json.dumps(prefs) % interpolation)
    1.92 +        for pref in prefs:
    1.93 +            prefs[pref] = Preferences.cast(prefs[pref])
    1.94 +
    1.95 +        kwargs = {
    1.96 +            'addons': self.getExtensionsToInstall(options),
    1.97 +            'apps': self.webapps,
    1.98 +            'locations': self.locations_file,
    1.99 +            'preferences': prefs,
   1.100 +            'proxy': {"remote": options.webServer}
   1.101 +        }
   1.102 +
   1.103 +        if options.profile:
   1.104 +            self.profile = Profile.clone(options.profile, **kwargs)
   1.105 +        else:
   1.106 +            self.profile = Profile(**kwargs)
   1.107 +
   1.108 +        options.profilePath = self.profile.profile
   1.109 +        # TODO bug 839108 - mozprofile should probably handle this
   1.110 +        manifest = self.addChromeToProfile(options)
   1.111 +        self.copyExtraFilesToProfile(options)
   1.112 +        return manifest
   1.113 +
   1.114 +    def run_tests(self, options):
   1.115 +        """ Prepare, configure, run tests and cleanup """
   1.116 +
   1.117 +        self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log")
   1.118 +        manifest = self.build_profile(options)
   1.119 +
   1.120 +        self.startServers(options, None)
   1.121 +        self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'})
   1.122 +        self.test_script_args.append(not options.emulator)
   1.123 +        self.test_script_args.append(options.wifi)
   1.124 +
   1.125 +        if options.debugger or not options.autorun:
   1.126 +            timeout = None
   1.127 +        else:
   1.128 +            if not options.timeout:
   1.129 +                if mozinfo.info['debug']:
   1.130 +                    options.timeout = 420
   1.131 +                else:
   1.132 +                    options.timeout = 300
   1.133 +            timeout = options.timeout + 30.0
   1.134 +
   1.135 +        log.info("runtestsb2g.py | Running tests: start.")
   1.136 +        status = 0
   1.137 +        try:
   1.138 +            runner_args = { 'profile': self.profile,
   1.139 +                            'devicemanager': self._dm,
   1.140 +                            'marionette': self.marionette,
   1.141 +                            'remote_test_root': self.remote_test_root,
   1.142 +                            'symbols_path': options.symbolsPath,
   1.143 +                            'test_script': self.test_script,
   1.144 +                            'test_script_args': self.test_script_args }
   1.145 +            self.runner = B2GRunner(**runner_args)
   1.146 +            self.runner.start(outputTimeout=timeout)
   1.147 +            status = self.runner.wait()
   1.148 +            if status is None:
   1.149 +                # the runner has timed out
   1.150 +                status = 124
   1.151 +        except KeyboardInterrupt:
   1.152 +            log.info("runtests.py | Received keyboard interrupt.\n");
   1.153 +            status = -1
   1.154 +        except:
   1.155 +            traceback.print_exc()
   1.156 +            log.error("Automation Error: Received unexpected exception while running application\n")
   1.157 +            self.runner.check_for_crashes()
   1.158 +            status = 1
   1.159 +
   1.160 +        self.stopServers()
   1.161 +
   1.162 +        log.info("runtestsb2g.py | Running tests: end.")
   1.163 +
   1.164 +        if manifest is not None:
   1.165 +            self.cleanup(manifest, options)
   1.166 +        return status
   1.167 +
   1.168 +
   1.169 +class B2GDeviceMochitest(B2GMochitest):
   1.170 +
   1.171 +    _dm = None
   1.172 +
   1.173 +    def __init__(self, marionette, devicemanager, profile_data_dir,
   1.174 +                 local_binary_dir, remote_test_root=None, remote_log_file=None):
   1.175 +        B2GMochitest.__init__(self, marionette, out_of_process=True, profile_data_dir=profile_data_dir)
   1.176 +        self._dm = devicemanager
   1.177 +        self.remote_test_root = remote_test_root or self._dm.getDeviceRoot()
   1.178 +        self.remote_profile = posixpath.join(self.remote_test_root, 'profile')
   1.179 +        self.remote_log = remote_log_file or posixpath.join(self.remote_test_root, 'log', 'mochitest.log')
   1.180 +        self.local_log = None
   1.181 +        self.local_binary_dir = local_binary_dir
   1.182 +
   1.183 +        if not self._dm.dirExists(posixpath.dirname(self.remote_log)):
   1.184 +            self._dm.mkDirs(self.remote_log)
   1.185 +
   1.186 +    def cleanup(self, manifest, options):
   1.187 +        if self.local_log:
   1.188 +            self._dm.getFile(self.remote_log, self.local_log)
   1.189 +            self._dm.removeFile(self.remote_log)
   1.190 +
   1.191 +        if options.pidFile != "":
   1.192 +            try:
   1.193 +                os.remove(options.pidFile)
   1.194 +                os.remove(options.pidFile + ".xpcshell.pid")
   1.195 +            except:
   1.196 +                print "Warning: cleaning up pidfile '%s' was unsuccessful from the test harness" % options.pidFile
   1.197 +
   1.198 +        # stop and clean up the runner
   1.199 +        if getattr(self, 'runner', False):
   1.200 +            self.runner.cleanup()
   1.201 +            self.runner = None
   1.202 +
   1.203 +    def startServers(self, options, debuggerInfo):
   1.204 +        """ Create the servers on the host and start them up """
   1.205 +        savedXre = options.xrePath
   1.206 +        savedUtility = options.utilityPath
   1.207 +        savedProfie = options.profilePath
   1.208 +        options.xrePath = self.local_binary_dir
   1.209 +        options.utilityPath = self.local_binary_dir
   1.210 +        options.profilePath = tempfile.mkdtemp()
   1.211 +
   1.212 +        MochitestUtilsMixin.startServers(self, options, debuggerInfo)
   1.213 +
   1.214 +        options.xrePath = savedXre
   1.215 +        options.utilityPath = savedUtility
   1.216 +        options.profilePath = savedProfie
   1.217 +
   1.218 +    def buildURLOptions(self, options, env):
   1.219 +        self.local_log = options.logFile
   1.220 +        options.logFile = self.remote_log
   1.221 +        options.profilePath = self.profile.profile
   1.222 +        retVal = super(B2GDeviceMochitest, self).buildURLOptions(options, env)
   1.223 +
   1.224 +        self.setup_common_options(options)
   1.225 +
   1.226 +        options.profilePath = self.remote_profile
   1.227 +        options.logFile = self.local_log
   1.228 +        return retVal
   1.229 +
   1.230 +
   1.231 +class B2GDesktopMochitest(B2GMochitest, Mochitest):
   1.232 +
   1.233 +    def __init__(self, marionette, profile_data_dir):
   1.234 +        B2GMochitest.__init__(self, marionette, out_of_process=False, profile_data_dir=profile_data_dir)
   1.235 +        Mochitest.__init__(self)
   1.236 +        self.certdbNew = True
   1.237 +
   1.238 +    def runMarionetteScript(self, marionette, test_script, test_script_args):
   1.239 +        assert(marionette.wait_for_port())
   1.240 +        marionette.start_session()
   1.241 +        marionette.set_context(marionette.CONTEXT_CHROME)
   1.242 +
   1.243 +        if os.path.isfile(test_script):
   1.244 +            f = open(test_script, 'r')
   1.245 +            test_script = f.read()
   1.246 +            f.close()
   1.247 +        self.marionette.execute_script(test_script,
   1.248 +                                       script_args=test_script_args)
   1.249 +
   1.250 +    def startTests(self):
   1.251 +        # This is run in a separate thread because otherwise, the app's
   1.252 +        # stdout buffer gets filled (which gets drained only after this
   1.253 +        # function returns, by waitForFinish), which causes the app to hang.
   1.254 +        thread = threading.Thread(target=self.runMarionetteScript,
   1.255 +                                  args=(self.marionette,
   1.256 +                                        self.test_script,
   1.257 +                                        self.test_script_args))
   1.258 +        thread.start()
   1.259 +
   1.260 +    def buildURLOptions(self, options, env):
   1.261 +        retVal = super(B2GDesktopMochitest, self).buildURLOptions(options, env)
   1.262 +
   1.263 +        self.setup_common_options(options)
   1.264 +
   1.265 +        # Copy the extensions to the B2G bundles dir.
   1.266 +        extensionDir = os.path.join(options.profilePath, 'extensions', 'staged')
   1.267 +        bundlesDir = os.path.join(os.path.dirname(options.app),
   1.268 +                                  'distribution', 'bundles')
   1.269 +
   1.270 +        for filename in os.listdir(extensionDir):
   1.271 +            shutil.rmtree(os.path.join(bundlesDir, filename), True)
   1.272 +            shutil.copytree(os.path.join(extensionDir, filename),
   1.273 +                            os.path.join(bundlesDir, filename))
   1.274 +
   1.275 +        return retVal
   1.276 +
   1.277 +    def buildProfile(self, options):
   1.278 +        return self.build_profile(options)
   1.279 +
   1.280 +
   1.281 +def run_remote_mochitests(parser, options):
   1.282 +    # create our Marionette instance
   1.283 +    kwargs = {}
   1.284 +    if options.emulator:
   1.285 +        kwargs['emulator'] = options.emulator
   1.286 +        if options.noWindow:
   1.287 +            kwargs['noWindow'] = True
   1.288 +        if options.geckoPath:
   1.289 +            kwargs['gecko_path'] = options.geckoPath
   1.290 +        if options.logcat_dir:
   1.291 +            kwargs['logcat_dir'] = options.logcat_dir
   1.292 +        if options.busybox:
   1.293 +            kwargs['busybox'] = options.busybox
   1.294 +        if options.symbolsPath:
   1.295 +            kwargs['symbols_path'] = options.symbolsPath
   1.296 +    # needless to say sdcard is only valid if using an emulator
   1.297 +    if options.sdcard:
   1.298 +        kwargs['sdcard'] = options.sdcard
   1.299 +    if options.b2gPath:
   1.300 +        kwargs['homedir'] = options.b2gPath
   1.301 +    if options.marionette:
   1.302 +        host, port = options.marionette.split(':')
   1.303 +        kwargs['host'] = host
   1.304 +        kwargs['port'] = int(port)
   1.305 +
   1.306 +    marionette = Marionette.getMarionetteOrExit(**kwargs)
   1.307 +
   1.308 +    if options.emulator:
   1.309 +        dm = marionette.emulator.dm
   1.310 +    else:
   1.311 +        # create the DeviceManager
   1.312 +        kwargs = {'adbPath': options.adbPath,
   1.313 +                  'deviceRoot': options.remoteTestRoot}
   1.314 +        if options.deviceIP:
   1.315 +            kwargs.update({'host': options.deviceIP,
   1.316 +                           'port': options.devicePort})
   1.317 +        dm = DeviceManagerADB(**kwargs)
   1.318 +
   1.319 +    options = parser.verifyRemoteOptions(options)
   1.320 +    if (options == None):
   1.321 +        print "ERROR: Invalid options specified, use --help for a list of valid options"
   1.322 +        sys.exit(1)
   1.323 +
   1.324 +    mochitest = B2GDeviceMochitest(marionette, dm, options.profile_data_dir, options.xrePath,
   1.325 +                                   remote_test_root=options.remoteTestRoot,
   1.326 +                                   remote_log_file=options.remoteLogFile)
   1.327 +
   1.328 +    options = parser.verifyOptions(options, mochitest)
   1.329 +    if (options == None):
   1.330 +        sys.exit(1)
   1.331 +
   1.332 +    retVal = 1
   1.333 +    try:
   1.334 +        mochitest.cleanup(None, options)
   1.335 +        retVal = mochitest.run_tests(options)
   1.336 +    except:
   1.337 +        print "Automation Error: Exception caught while running tests"
   1.338 +        traceback.print_exc()
   1.339 +        mochitest.stopServers()
   1.340 +        try:
   1.341 +            mochitest.cleanup(None, options)
   1.342 +        except:
   1.343 +            pass
   1.344 +        retVal = 1
   1.345 +
   1.346 +    sys.exit(retVal)
   1.347 +
   1.348 +def run_desktop_mochitests(parser, options):
   1.349 +    # create our Marionette instance
   1.350 +    kwargs = {}
   1.351 +    if options.marionette:
   1.352 +        host, port = options.marionette.split(':')
   1.353 +        kwargs['host'] = host
   1.354 +        kwargs['port'] = int(port)
   1.355 +    marionette = Marionette.getMarionetteOrExit(**kwargs)
   1.356 +    mochitest = B2GDesktopMochitest(marionette, options.profile_data_dir)
   1.357 +
   1.358 +    # add a -bin suffix if b2g-bin exists, but just b2g was specified
   1.359 +    if options.app[-4:] != '-bin':
   1.360 +        if os.path.isfile("%s-bin" % options.app):
   1.361 +            options.app = "%s-bin" % options.app
   1.362 +
   1.363 +    options = MochitestOptions.verifyOptions(parser, options, mochitest)
   1.364 +    if options == None:
   1.365 +        sys.exit(1)
   1.366 +
   1.367 +    if options.desktop and not options.profile:
   1.368 +        raise Exception("must specify --profile when specifying --desktop")
   1.369 +
   1.370 +    options.browserArgs += ['-marionette']
   1.371 +
   1.372 +    sys.exit(mochitest.runTests(options, onLaunch=mochitest.startTests))
   1.373 +
   1.374 +def main():
   1.375 +    parser = B2GOptions()
   1.376 +    options, args = parser.parse_args()
   1.377 +
   1.378 +    if options.desktop:
   1.379 +        run_desktop_mochitests(parser, options)
   1.380 +    else:
   1.381 +        run_remote_mochitests(parser, options)
   1.382 +
   1.383 +if __name__ == "__main__":
   1.384 +    main()

mercurial