michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: # You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: import json michael@0: import os michael@0: import posixpath michael@0: import shutil michael@0: import sys michael@0: import tempfile michael@0: import threading michael@0: import time michael@0: import traceback michael@0: michael@0: here = os.path.abspath(os.path.dirname(__file__)) michael@0: sys.path.insert(0, here) michael@0: michael@0: from runtests import Mochitest michael@0: from runtests import MochitestUtilsMixin michael@0: from runtests import MochitestOptions michael@0: from runtests import MochitestServer michael@0: from mochitest_options import B2GOptions, MochitestOptions michael@0: michael@0: from marionette import Marionette michael@0: michael@0: from mozdevice import DeviceManagerADB michael@0: from mozprofile import Profile, Preferences michael@0: from mozrunner import B2GRunner michael@0: import mozlog michael@0: import mozinfo michael@0: import moznetwork michael@0: michael@0: log = mozlog.getLogger('Mochitest') michael@0: michael@0: class B2GMochitest(MochitestUtilsMixin): michael@0: def __init__(self, marionette, michael@0: out_of_process=True, michael@0: profile_data_dir=None, michael@0: locations=os.path.join(here, 'server-locations.txt')): michael@0: super(B2GMochitest, self).__init__() michael@0: self.marionette = marionette michael@0: self.out_of_process = out_of_process michael@0: self.locations_file = locations michael@0: self.preferences = [] michael@0: self.webapps = None michael@0: self.test_script = os.path.join(here, 'b2g_start_script.js') michael@0: self.test_script_args = [self.out_of_process] michael@0: self.product = 'b2g' michael@0: michael@0: if profile_data_dir: michael@0: self.preferences = [os.path.join(profile_data_dir, f) michael@0: for f in os.listdir(profile_data_dir) if f.startswith('pref')] michael@0: self.webapps = [os.path.join(profile_data_dir, f) michael@0: for f in os.listdir(profile_data_dir) if f.startswith('webapp')] michael@0: michael@0: # mozinfo is populated by the parent class michael@0: if mozinfo.info['debug']: michael@0: self.SERVER_STARTUP_TIMEOUT = 180 michael@0: else: michael@0: self.SERVER_STARTUP_TIMEOUT = 90 michael@0: michael@0: def setup_common_options(self, options): michael@0: test_url = self.buildTestPath(options) michael@0: if len(self.urlOpts) > 0: michael@0: test_url += "?" + "&".join(self.urlOpts) michael@0: self.test_script_args.append(test_url) michael@0: michael@0: def buildTestPath(self, options): michael@0: # Skip over the manifest building that happens on desktop. michael@0: return self.buildTestURL(options) michael@0: michael@0: def build_profile(self, options): michael@0: # preferences michael@0: prefs = {} michael@0: for path in self.preferences: michael@0: prefs.update(Preferences.read_prefs(path)) michael@0: michael@0: for v in options.extraPrefs: michael@0: thispref = v.split("=", 1) michael@0: if len(thispref) < 2: michael@0: print "Error: syntax error in --setpref=" + v michael@0: sys.exit(1) michael@0: prefs[thispref[0]] = thispref[1] michael@0: michael@0: # interpolate the preferences michael@0: interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), michael@0: "OOP": "true" if self.out_of_process else "false" } michael@0: prefs = json.loads(json.dumps(prefs) % interpolation) michael@0: for pref in prefs: michael@0: prefs[pref] = Preferences.cast(prefs[pref]) michael@0: michael@0: kwargs = { michael@0: 'addons': self.getExtensionsToInstall(options), michael@0: 'apps': self.webapps, michael@0: 'locations': self.locations_file, michael@0: 'preferences': prefs, michael@0: 'proxy': {"remote": options.webServer} michael@0: } michael@0: michael@0: if options.profile: michael@0: self.profile = Profile.clone(options.profile, **kwargs) michael@0: else: michael@0: self.profile = Profile(**kwargs) michael@0: michael@0: options.profilePath = self.profile.profile michael@0: # TODO bug 839108 - mozprofile should probably handle this michael@0: manifest = self.addChromeToProfile(options) michael@0: self.copyExtraFilesToProfile(options) michael@0: return manifest michael@0: michael@0: def run_tests(self, options): michael@0: """ Prepare, configure, run tests and cleanup """ michael@0: michael@0: self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log") michael@0: manifest = self.build_profile(options) michael@0: michael@0: self.startServers(options, None) michael@0: self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) michael@0: self.test_script_args.append(not options.emulator) michael@0: self.test_script_args.append(options.wifi) michael@0: michael@0: if options.debugger or not options.autorun: michael@0: timeout = None michael@0: else: michael@0: if not options.timeout: michael@0: if mozinfo.info['debug']: michael@0: options.timeout = 420 michael@0: else: michael@0: options.timeout = 300 michael@0: timeout = options.timeout + 30.0 michael@0: michael@0: log.info("runtestsb2g.py | Running tests: start.") michael@0: status = 0 michael@0: try: michael@0: runner_args = { 'profile': self.profile, michael@0: 'devicemanager': self._dm, michael@0: 'marionette': self.marionette, michael@0: 'remote_test_root': self.remote_test_root, michael@0: 'symbols_path': options.symbolsPath, michael@0: 'test_script': self.test_script, michael@0: 'test_script_args': self.test_script_args } michael@0: self.runner = B2GRunner(**runner_args) michael@0: self.runner.start(outputTimeout=timeout) michael@0: status = self.runner.wait() michael@0: if status is None: michael@0: # the runner has timed out michael@0: status = 124 michael@0: except KeyboardInterrupt: michael@0: log.info("runtests.py | Received keyboard interrupt.\n"); michael@0: status = -1 michael@0: except: michael@0: traceback.print_exc() michael@0: log.error("Automation Error: Received unexpected exception while running application\n") michael@0: self.runner.check_for_crashes() michael@0: status = 1 michael@0: michael@0: self.stopServers() michael@0: michael@0: log.info("runtestsb2g.py | Running tests: end.") michael@0: michael@0: if manifest is not None: michael@0: self.cleanup(manifest, options) michael@0: return status michael@0: michael@0: michael@0: class B2GDeviceMochitest(B2GMochitest): michael@0: michael@0: _dm = None michael@0: michael@0: def __init__(self, marionette, devicemanager, profile_data_dir, michael@0: local_binary_dir, remote_test_root=None, remote_log_file=None): michael@0: B2GMochitest.__init__(self, marionette, out_of_process=True, profile_data_dir=profile_data_dir) michael@0: self._dm = devicemanager michael@0: self.remote_test_root = remote_test_root or self._dm.getDeviceRoot() michael@0: self.remote_profile = posixpath.join(self.remote_test_root, 'profile') michael@0: self.remote_log = remote_log_file or posixpath.join(self.remote_test_root, 'log', 'mochitest.log') michael@0: self.local_log = None michael@0: self.local_binary_dir = local_binary_dir michael@0: michael@0: if not self._dm.dirExists(posixpath.dirname(self.remote_log)): michael@0: self._dm.mkDirs(self.remote_log) michael@0: michael@0: def cleanup(self, manifest, options): michael@0: if self.local_log: michael@0: self._dm.getFile(self.remote_log, self.local_log) michael@0: self._dm.removeFile(self.remote_log) michael@0: michael@0: if options.pidFile != "": michael@0: try: michael@0: os.remove(options.pidFile) michael@0: os.remove(options.pidFile + ".xpcshell.pid") michael@0: except: michael@0: print "Warning: cleaning up pidfile '%s' was unsuccessful from the test harness" % options.pidFile michael@0: michael@0: # stop and clean up the runner michael@0: if getattr(self, 'runner', False): michael@0: self.runner.cleanup() michael@0: self.runner = None michael@0: michael@0: def startServers(self, options, debuggerInfo): michael@0: """ Create the servers on the host and start them up """ michael@0: savedXre = options.xrePath michael@0: savedUtility = options.utilityPath michael@0: savedProfie = options.profilePath michael@0: options.xrePath = self.local_binary_dir michael@0: options.utilityPath = self.local_binary_dir michael@0: options.profilePath = tempfile.mkdtemp() michael@0: michael@0: MochitestUtilsMixin.startServers(self, options, debuggerInfo) michael@0: michael@0: options.xrePath = savedXre michael@0: options.utilityPath = savedUtility michael@0: options.profilePath = savedProfie michael@0: michael@0: def buildURLOptions(self, options, env): michael@0: self.local_log = options.logFile michael@0: options.logFile = self.remote_log michael@0: options.profilePath = self.profile.profile michael@0: retVal = super(B2GDeviceMochitest, self).buildURLOptions(options, env) michael@0: michael@0: self.setup_common_options(options) michael@0: michael@0: options.profilePath = self.remote_profile michael@0: options.logFile = self.local_log michael@0: return retVal michael@0: michael@0: michael@0: class B2GDesktopMochitest(B2GMochitest, Mochitest): michael@0: michael@0: def __init__(self, marionette, profile_data_dir): michael@0: B2GMochitest.__init__(self, marionette, out_of_process=False, profile_data_dir=profile_data_dir) michael@0: Mochitest.__init__(self) michael@0: self.certdbNew = True michael@0: michael@0: def runMarionetteScript(self, marionette, test_script, test_script_args): michael@0: assert(marionette.wait_for_port()) michael@0: marionette.start_session() michael@0: marionette.set_context(marionette.CONTEXT_CHROME) michael@0: michael@0: if os.path.isfile(test_script): michael@0: f = open(test_script, 'r') michael@0: test_script = f.read() michael@0: f.close() michael@0: self.marionette.execute_script(test_script, michael@0: script_args=test_script_args) michael@0: michael@0: def startTests(self): michael@0: # This is run in a separate thread because otherwise, the app's michael@0: # stdout buffer gets filled (which gets drained only after this michael@0: # function returns, by waitForFinish), which causes the app to hang. michael@0: thread = threading.Thread(target=self.runMarionetteScript, michael@0: args=(self.marionette, michael@0: self.test_script, michael@0: self.test_script_args)) michael@0: thread.start() michael@0: michael@0: def buildURLOptions(self, options, env): michael@0: retVal = super(B2GDesktopMochitest, self).buildURLOptions(options, env) michael@0: michael@0: self.setup_common_options(options) michael@0: michael@0: # Copy the extensions to the B2G bundles dir. michael@0: extensionDir = os.path.join(options.profilePath, 'extensions', 'staged') michael@0: bundlesDir = os.path.join(os.path.dirname(options.app), michael@0: 'distribution', 'bundles') michael@0: michael@0: for filename in os.listdir(extensionDir): michael@0: shutil.rmtree(os.path.join(bundlesDir, filename), True) michael@0: shutil.copytree(os.path.join(extensionDir, filename), michael@0: os.path.join(bundlesDir, filename)) michael@0: michael@0: return retVal michael@0: michael@0: def buildProfile(self, options): michael@0: return self.build_profile(options) michael@0: michael@0: michael@0: def run_remote_mochitests(parser, options): michael@0: # create our Marionette instance michael@0: kwargs = {} michael@0: if options.emulator: michael@0: kwargs['emulator'] = options.emulator michael@0: if options.noWindow: michael@0: kwargs['noWindow'] = True michael@0: if options.geckoPath: michael@0: kwargs['gecko_path'] = options.geckoPath michael@0: if options.logcat_dir: michael@0: kwargs['logcat_dir'] = options.logcat_dir michael@0: if options.busybox: michael@0: kwargs['busybox'] = options.busybox michael@0: if options.symbolsPath: michael@0: kwargs['symbols_path'] = options.symbolsPath michael@0: # needless to say sdcard is only valid if using an emulator michael@0: if options.sdcard: michael@0: kwargs['sdcard'] = options.sdcard michael@0: if options.b2gPath: michael@0: kwargs['homedir'] = options.b2gPath michael@0: if options.marionette: michael@0: host, port = options.marionette.split(':') michael@0: kwargs['host'] = host michael@0: kwargs['port'] = int(port) michael@0: michael@0: marionette = Marionette.getMarionetteOrExit(**kwargs) michael@0: michael@0: if options.emulator: michael@0: dm = marionette.emulator.dm michael@0: else: michael@0: # create the DeviceManager michael@0: kwargs = {'adbPath': options.adbPath, michael@0: 'deviceRoot': options.remoteTestRoot} michael@0: if options.deviceIP: michael@0: kwargs.update({'host': options.deviceIP, michael@0: 'port': options.devicePort}) michael@0: dm = DeviceManagerADB(**kwargs) michael@0: michael@0: options = parser.verifyRemoteOptions(options) michael@0: if (options == None): michael@0: print "ERROR: Invalid options specified, use --help for a list of valid options" michael@0: sys.exit(1) michael@0: michael@0: mochitest = B2GDeviceMochitest(marionette, dm, options.profile_data_dir, options.xrePath, michael@0: remote_test_root=options.remoteTestRoot, michael@0: remote_log_file=options.remoteLogFile) michael@0: michael@0: options = parser.verifyOptions(options, mochitest) michael@0: if (options == None): michael@0: sys.exit(1) michael@0: michael@0: retVal = 1 michael@0: try: michael@0: mochitest.cleanup(None, options) michael@0: retVal = mochitest.run_tests(options) michael@0: except: michael@0: print "Automation Error: Exception caught while running tests" michael@0: traceback.print_exc() michael@0: mochitest.stopServers() michael@0: try: michael@0: mochitest.cleanup(None, options) michael@0: except: michael@0: pass michael@0: retVal = 1 michael@0: michael@0: sys.exit(retVal) michael@0: michael@0: def run_desktop_mochitests(parser, options): michael@0: # create our Marionette instance michael@0: kwargs = {} michael@0: if options.marionette: michael@0: host, port = options.marionette.split(':') michael@0: kwargs['host'] = host michael@0: kwargs['port'] = int(port) michael@0: marionette = Marionette.getMarionetteOrExit(**kwargs) michael@0: mochitest = B2GDesktopMochitest(marionette, options.profile_data_dir) michael@0: michael@0: # add a -bin suffix if b2g-bin exists, but just b2g was specified michael@0: if options.app[-4:] != '-bin': michael@0: if os.path.isfile("%s-bin" % options.app): michael@0: options.app = "%s-bin" % options.app michael@0: michael@0: options = MochitestOptions.verifyOptions(parser, options, mochitest) michael@0: if options == None: michael@0: sys.exit(1) michael@0: michael@0: if options.desktop and not options.profile: michael@0: raise Exception("must specify --profile when specifying --desktop") michael@0: michael@0: options.browserArgs += ['-marionette'] michael@0: michael@0: sys.exit(mochitest.runTests(options, onLaunch=mochitest.startTests)) michael@0: michael@0: def main(): michael@0: parser = B2GOptions() michael@0: options, args = parser.parse_args() michael@0: michael@0: if options.desktop: michael@0: run_desktop_mochitests(parser, options) michael@0: else: michael@0: run_remote_mochitests(parser, options) michael@0: michael@0: if __name__ == "__main__": michael@0: main()