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()