Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | # You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | import json |
michael@0 | 6 | import os |
michael@0 | 7 | import posixpath |
michael@0 | 8 | import shutil |
michael@0 | 9 | import sys |
michael@0 | 10 | import tempfile |
michael@0 | 11 | import threading |
michael@0 | 12 | import time |
michael@0 | 13 | import traceback |
michael@0 | 14 | |
michael@0 | 15 | here = os.path.abspath(os.path.dirname(__file__)) |
michael@0 | 16 | sys.path.insert(0, here) |
michael@0 | 17 | |
michael@0 | 18 | from runtests import Mochitest |
michael@0 | 19 | from runtests import MochitestUtilsMixin |
michael@0 | 20 | from runtests import MochitestOptions |
michael@0 | 21 | from runtests import MochitestServer |
michael@0 | 22 | from mochitest_options import B2GOptions, MochitestOptions |
michael@0 | 23 | |
michael@0 | 24 | from marionette import Marionette |
michael@0 | 25 | |
michael@0 | 26 | from mozdevice import DeviceManagerADB |
michael@0 | 27 | from mozprofile import Profile, Preferences |
michael@0 | 28 | from mozrunner import B2GRunner |
michael@0 | 29 | import mozlog |
michael@0 | 30 | import mozinfo |
michael@0 | 31 | import moznetwork |
michael@0 | 32 | |
michael@0 | 33 | log = mozlog.getLogger('Mochitest') |
michael@0 | 34 | |
michael@0 | 35 | class B2GMochitest(MochitestUtilsMixin): |
michael@0 | 36 | def __init__(self, marionette, |
michael@0 | 37 | out_of_process=True, |
michael@0 | 38 | profile_data_dir=None, |
michael@0 | 39 | locations=os.path.join(here, 'server-locations.txt')): |
michael@0 | 40 | super(B2GMochitest, self).__init__() |
michael@0 | 41 | self.marionette = marionette |
michael@0 | 42 | self.out_of_process = out_of_process |
michael@0 | 43 | self.locations_file = locations |
michael@0 | 44 | self.preferences = [] |
michael@0 | 45 | self.webapps = None |
michael@0 | 46 | self.test_script = os.path.join(here, 'b2g_start_script.js') |
michael@0 | 47 | self.test_script_args = [self.out_of_process] |
michael@0 | 48 | self.product = 'b2g' |
michael@0 | 49 | |
michael@0 | 50 | if profile_data_dir: |
michael@0 | 51 | self.preferences = [os.path.join(profile_data_dir, f) |
michael@0 | 52 | for f in os.listdir(profile_data_dir) if f.startswith('pref')] |
michael@0 | 53 | self.webapps = [os.path.join(profile_data_dir, f) |
michael@0 | 54 | for f in os.listdir(profile_data_dir) if f.startswith('webapp')] |
michael@0 | 55 | |
michael@0 | 56 | # mozinfo is populated by the parent class |
michael@0 | 57 | if mozinfo.info['debug']: |
michael@0 | 58 | self.SERVER_STARTUP_TIMEOUT = 180 |
michael@0 | 59 | else: |
michael@0 | 60 | self.SERVER_STARTUP_TIMEOUT = 90 |
michael@0 | 61 | |
michael@0 | 62 | def setup_common_options(self, options): |
michael@0 | 63 | test_url = self.buildTestPath(options) |
michael@0 | 64 | if len(self.urlOpts) > 0: |
michael@0 | 65 | test_url += "?" + "&".join(self.urlOpts) |
michael@0 | 66 | self.test_script_args.append(test_url) |
michael@0 | 67 | |
michael@0 | 68 | def buildTestPath(self, options): |
michael@0 | 69 | # Skip over the manifest building that happens on desktop. |
michael@0 | 70 | return self.buildTestURL(options) |
michael@0 | 71 | |
michael@0 | 72 | def build_profile(self, options): |
michael@0 | 73 | # preferences |
michael@0 | 74 | prefs = {} |
michael@0 | 75 | for path in self.preferences: |
michael@0 | 76 | prefs.update(Preferences.read_prefs(path)) |
michael@0 | 77 | |
michael@0 | 78 | for v in options.extraPrefs: |
michael@0 | 79 | thispref = v.split("=", 1) |
michael@0 | 80 | if len(thispref) < 2: |
michael@0 | 81 | print "Error: syntax error in --setpref=" + v |
michael@0 | 82 | sys.exit(1) |
michael@0 | 83 | prefs[thispref[0]] = thispref[1] |
michael@0 | 84 | |
michael@0 | 85 | # interpolate the preferences |
michael@0 | 86 | interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), |
michael@0 | 87 | "OOP": "true" if self.out_of_process else "false" } |
michael@0 | 88 | prefs = json.loads(json.dumps(prefs) % interpolation) |
michael@0 | 89 | for pref in prefs: |
michael@0 | 90 | prefs[pref] = Preferences.cast(prefs[pref]) |
michael@0 | 91 | |
michael@0 | 92 | kwargs = { |
michael@0 | 93 | 'addons': self.getExtensionsToInstall(options), |
michael@0 | 94 | 'apps': self.webapps, |
michael@0 | 95 | 'locations': self.locations_file, |
michael@0 | 96 | 'preferences': prefs, |
michael@0 | 97 | 'proxy': {"remote": options.webServer} |
michael@0 | 98 | } |
michael@0 | 99 | |
michael@0 | 100 | if options.profile: |
michael@0 | 101 | self.profile = Profile.clone(options.profile, **kwargs) |
michael@0 | 102 | else: |
michael@0 | 103 | self.profile = Profile(**kwargs) |
michael@0 | 104 | |
michael@0 | 105 | options.profilePath = self.profile.profile |
michael@0 | 106 | # TODO bug 839108 - mozprofile should probably handle this |
michael@0 | 107 | manifest = self.addChromeToProfile(options) |
michael@0 | 108 | self.copyExtraFilesToProfile(options) |
michael@0 | 109 | return manifest |
michael@0 | 110 | |
michael@0 | 111 | def run_tests(self, options): |
michael@0 | 112 | """ Prepare, configure, run tests and cleanup """ |
michael@0 | 113 | |
michael@0 | 114 | self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log") |
michael@0 | 115 | manifest = self.build_profile(options) |
michael@0 | 116 | |
michael@0 | 117 | self.startServers(options, None) |
michael@0 | 118 | self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) |
michael@0 | 119 | self.test_script_args.append(not options.emulator) |
michael@0 | 120 | self.test_script_args.append(options.wifi) |
michael@0 | 121 | |
michael@0 | 122 | if options.debugger or not options.autorun: |
michael@0 | 123 | timeout = None |
michael@0 | 124 | else: |
michael@0 | 125 | if not options.timeout: |
michael@0 | 126 | if mozinfo.info['debug']: |
michael@0 | 127 | options.timeout = 420 |
michael@0 | 128 | else: |
michael@0 | 129 | options.timeout = 300 |
michael@0 | 130 | timeout = options.timeout + 30.0 |
michael@0 | 131 | |
michael@0 | 132 | log.info("runtestsb2g.py | Running tests: start.") |
michael@0 | 133 | status = 0 |
michael@0 | 134 | try: |
michael@0 | 135 | runner_args = { 'profile': self.profile, |
michael@0 | 136 | 'devicemanager': self._dm, |
michael@0 | 137 | 'marionette': self.marionette, |
michael@0 | 138 | 'remote_test_root': self.remote_test_root, |
michael@0 | 139 | 'symbols_path': options.symbolsPath, |
michael@0 | 140 | 'test_script': self.test_script, |
michael@0 | 141 | 'test_script_args': self.test_script_args } |
michael@0 | 142 | self.runner = B2GRunner(**runner_args) |
michael@0 | 143 | self.runner.start(outputTimeout=timeout) |
michael@0 | 144 | status = self.runner.wait() |
michael@0 | 145 | if status is None: |
michael@0 | 146 | # the runner has timed out |
michael@0 | 147 | status = 124 |
michael@0 | 148 | except KeyboardInterrupt: |
michael@0 | 149 | log.info("runtests.py | Received keyboard interrupt.\n"); |
michael@0 | 150 | status = -1 |
michael@0 | 151 | except: |
michael@0 | 152 | traceback.print_exc() |
michael@0 | 153 | log.error("Automation Error: Received unexpected exception while running application\n") |
michael@0 | 154 | self.runner.check_for_crashes() |
michael@0 | 155 | status = 1 |
michael@0 | 156 | |
michael@0 | 157 | self.stopServers() |
michael@0 | 158 | |
michael@0 | 159 | log.info("runtestsb2g.py | Running tests: end.") |
michael@0 | 160 | |
michael@0 | 161 | if manifest is not None: |
michael@0 | 162 | self.cleanup(manifest, options) |
michael@0 | 163 | return status |
michael@0 | 164 | |
michael@0 | 165 | |
michael@0 | 166 | class B2GDeviceMochitest(B2GMochitest): |
michael@0 | 167 | |
michael@0 | 168 | _dm = None |
michael@0 | 169 | |
michael@0 | 170 | def __init__(self, marionette, devicemanager, profile_data_dir, |
michael@0 | 171 | local_binary_dir, remote_test_root=None, remote_log_file=None): |
michael@0 | 172 | B2GMochitest.__init__(self, marionette, out_of_process=True, profile_data_dir=profile_data_dir) |
michael@0 | 173 | self._dm = devicemanager |
michael@0 | 174 | self.remote_test_root = remote_test_root or self._dm.getDeviceRoot() |
michael@0 | 175 | self.remote_profile = posixpath.join(self.remote_test_root, 'profile') |
michael@0 | 176 | self.remote_log = remote_log_file or posixpath.join(self.remote_test_root, 'log', 'mochitest.log') |
michael@0 | 177 | self.local_log = None |
michael@0 | 178 | self.local_binary_dir = local_binary_dir |
michael@0 | 179 | |
michael@0 | 180 | if not self._dm.dirExists(posixpath.dirname(self.remote_log)): |
michael@0 | 181 | self._dm.mkDirs(self.remote_log) |
michael@0 | 182 | |
michael@0 | 183 | def cleanup(self, manifest, options): |
michael@0 | 184 | if self.local_log: |
michael@0 | 185 | self._dm.getFile(self.remote_log, self.local_log) |
michael@0 | 186 | self._dm.removeFile(self.remote_log) |
michael@0 | 187 | |
michael@0 | 188 | if options.pidFile != "": |
michael@0 | 189 | try: |
michael@0 | 190 | os.remove(options.pidFile) |
michael@0 | 191 | os.remove(options.pidFile + ".xpcshell.pid") |
michael@0 | 192 | except: |
michael@0 | 193 | print "Warning: cleaning up pidfile '%s' was unsuccessful from the test harness" % options.pidFile |
michael@0 | 194 | |
michael@0 | 195 | # stop and clean up the runner |
michael@0 | 196 | if getattr(self, 'runner', False): |
michael@0 | 197 | self.runner.cleanup() |
michael@0 | 198 | self.runner = None |
michael@0 | 199 | |
michael@0 | 200 | def startServers(self, options, debuggerInfo): |
michael@0 | 201 | """ Create the servers on the host and start them up """ |
michael@0 | 202 | savedXre = options.xrePath |
michael@0 | 203 | savedUtility = options.utilityPath |
michael@0 | 204 | savedProfie = options.profilePath |
michael@0 | 205 | options.xrePath = self.local_binary_dir |
michael@0 | 206 | options.utilityPath = self.local_binary_dir |
michael@0 | 207 | options.profilePath = tempfile.mkdtemp() |
michael@0 | 208 | |
michael@0 | 209 | MochitestUtilsMixin.startServers(self, options, debuggerInfo) |
michael@0 | 210 | |
michael@0 | 211 | options.xrePath = savedXre |
michael@0 | 212 | options.utilityPath = savedUtility |
michael@0 | 213 | options.profilePath = savedProfie |
michael@0 | 214 | |
michael@0 | 215 | def buildURLOptions(self, options, env): |
michael@0 | 216 | self.local_log = options.logFile |
michael@0 | 217 | options.logFile = self.remote_log |
michael@0 | 218 | options.profilePath = self.profile.profile |
michael@0 | 219 | retVal = super(B2GDeviceMochitest, self).buildURLOptions(options, env) |
michael@0 | 220 | |
michael@0 | 221 | self.setup_common_options(options) |
michael@0 | 222 | |
michael@0 | 223 | options.profilePath = self.remote_profile |
michael@0 | 224 | options.logFile = self.local_log |
michael@0 | 225 | return retVal |
michael@0 | 226 | |
michael@0 | 227 | |
michael@0 | 228 | class B2GDesktopMochitest(B2GMochitest, Mochitest): |
michael@0 | 229 | |
michael@0 | 230 | def __init__(self, marionette, profile_data_dir): |
michael@0 | 231 | B2GMochitest.__init__(self, marionette, out_of_process=False, profile_data_dir=profile_data_dir) |
michael@0 | 232 | Mochitest.__init__(self) |
michael@0 | 233 | self.certdbNew = True |
michael@0 | 234 | |
michael@0 | 235 | def runMarionetteScript(self, marionette, test_script, test_script_args): |
michael@0 | 236 | assert(marionette.wait_for_port()) |
michael@0 | 237 | marionette.start_session() |
michael@0 | 238 | marionette.set_context(marionette.CONTEXT_CHROME) |
michael@0 | 239 | |
michael@0 | 240 | if os.path.isfile(test_script): |
michael@0 | 241 | f = open(test_script, 'r') |
michael@0 | 242 | test_script = f.read() |
michael@0 | 243 | f.close() |
michael@0 | 244 | self.marionette.execute_script(test_script, |
michael@0 | 245 | script_args=test_script_args) |
michael@0 | 246 | |
michael@0 | 247 | def startTests(self): |
michael@0 | 248 | # This is run in a separate thread because otherwise, the app's |
michael@0 | 249 | # stdout buffer gets filled (which gets drained only after this |
michael@0 | 250 | # function returns, by waitForFinish), which causes the app to hang. |
michael@0 | 251 | thread = threading.Thread(target=self.runMarionetteScript, |
michael@0 | 252 | args=(self.marionette, |
michael@0 | 253 | self.test_script, |
michael@0 | 254 | self.test_script_args)) |
michael@0 | 255 | thread.start() |
michael@0 | 256 | |
michael@0 | 257 | def buildURLOptions(self, options, env): |
michael@0 | 258 | retVal = super(B2GDesktopMochitest, self).buildURLOptions(options, env) |
michael@0 | 259 | |
michael@0 | 260 | self.setup_common_options(options) |
michael@0 | 261 | |
michael@0 | 262 | # Copy the extensions to the B2G bundles dir. |
michael@0 | 263 | extensionDir = os.path.join(options.profilePath, 'extensions', 'staged') |
michael@0 | 264 | bundlesDir = os.path.join(os.path.dirname(options.app), |
michael@0 | 265 | 'distribution', 'bundles') |
michael@0 | 266 | |
michael@0 | 267 | for filename in os.listdir(extensionDir): |
michael@0 | 268 | shutil.rmtree(os.path.join(bundlesDir, filename), True) |
michael@0 | 269 | shutil.copytree(os.path.join(extensionDir, filename), |
michael@0 | 270 | os.path.join(bundlesDir, filename)) |
michael@0 | 271 | |
michael@0 | 272 | return retVal |
michael@0 | 273 | |
michael@0 | 274 | def buildProfile(self, options): |
michael@0 | 275 | return self.build_profile(options) |
michael@0 | 276 | |
michael@0 | 277 | |
michael@0 | 278 | def run_remote_mochitests(parser, options): |
michael@0 | 279 | # create our Marionette instance |
michael@0 | 280 | kwargs = {} |
michael@0 | 281 | if options.emulator: |
michael@0 | 282 | kwargs['emulator'] = options.emulator |
michael@0 | 283 | if options.noWindow: |
michael@0 | 284 | kwargs['noWindow'] = True |
michael@0 | 285 | if options.geckoPath: |
michael@0 | 286 | kwargs['gecko_path'] = options.geckoPath |
michael@0 | 287 | if options.logcat_dir: |
michael@0 | 288 | kwargs['logcat_dir'] = options.logcat_dir |
michael@0 | 289 | if options.busybox: |
michael@0 | 290 | kwargs['busybox'] = options.busybox |
michael@0 | 291 | if options.symbolsPath: |
michael@0 | 292 | kwargs['symbols_path'] = options.symbolsPath |
michael@0 | 293 | # needless to say sdcard is only valid if using an emulator |
michael@0 | 294 | if options.sdcard: |
michael@0 | 295 | kwargs['sdcard'] = options.sdcard |
michael@0 | 296 | if options.b2gPath: |
michael@0 | 297 | kwargs['homedir'] = options.b2gPath |
michael@0 | 298 | if options.marionette: |
michael@0 | 299 | host, port = options.marionette.split(':') |
michael@0 | 300 | kwargs['host'] = host |
michael@0 | 301 | kwargs['port'] = int(port) |
michael@0 | 302 | |
michael@0 | 303 | marionette = Marionette.getMarionetteOrExit(**kwargs) |
michael@0 | 304 | |
michael@0 | 305 | if options.emulator: |
michael@0 | 306 | dm = marionette.emulator.dm |
michael@0 | 307 | else: |
michael@0 | 308 | # create the DeviceManager |
michael@0 | 309 | kwargs = {'adbPath': options.adbPath, |
michael@0 | 310 | 'deviceRoot': options.remoteTestRoot} |
michael@0 | 311 | if options.deviceIP: |
michael@0 | 312 | kwargs.update({'host': options.deviceIP, |
michael@0 | 313 | 'port': options.devicePort}) |
michael@0 | 314 | dm = DeviceManagerADB(**kwargs) |
michael@0 | 315 | |
michael@0 | 316 | options = parser.verifyRemoteOptions(options) |
michael@0 | 317 | if (options == None): |
michael@0 | 318 | print "ERROR: Invalid options specified, use --help for a list of valid options" |
michael@0 | 319 | sys.exit(1) |
michael@0 | 320 | |
michael@0 | 321 | mochitest = B2GDeviceMochitest(marionette, dm, options.profile_data_dir, options.xrePath, |
michael@0 | 322 | remote_test_root=options.remoteTestRoot, |
michael@0 | 323 | remote_log_file=options.remoteLogFile) |
michael@0 | 324 | |
michael@0 | 325 | options = parser.verifyOptions(options, mochitest) |
michael@0 | 326 | if (options == None): |
michael@0 | 327 | sys.exit(1) |
michael@0 | 328 | |
michael@0 | 329 | retVal = 1 |
michael@0 | 330 | try: |
michael@0 | 331 | mochitest.cleanup(None, options) |
michael@0 | 332 | retVal = mochitest.run_tests(options) |
michael@0 | 333 | except: |
michael@0 | 334 | print "Automation Error: Exception caught while running tests" |
michael@0 | 335 | traceback.print_exc() |
michael@0 | 336 | mochitest.stopServers() |
michael@0 | 337 | try: |
michael@0 | 338 | mochitest.cleanup(None, options) |
michael@0 | 339 | except: |
michael@0 | 340 | pass |
michael@0 | 341 | retVal = 1 |
michael@0 | 342 | |
michael@0 | 343 | sys.exit(retVal) |
michael@0 | 344 | |
michael@0 | 345 | def run_desktop_mochitests(parser, options): |
michael@0 | 346 | # create our Marionette instance |
michael@0 | 347 | kwargs = {} |
michael@0 | 348 | if options.marionette: |
michael@0 | 349 | host, port = options.marionette.split(':') |
michael@0 | 350 | kwargs['host'] = host |
michael@0 | 351 | kwargs['port'] = int(port) |
michael@0 | 352 | marionette = Marionette.getMarionetteOrExit(**kwargs) |
michael@0 | 353 | mochitest = B2GDesktopMochitest(marionette, options.profile_data_dir) |
michael@0 | 354 | |
michael@0 | 355 | # add a -bin suffix if b2g-bin exists, but just b2g was specified |
michael@0 | 356 | if options.app[-4:] != '-bin': |
michael@0 | 357 | if os.path.isfile("%s-bin" % options.app): |
michael@0 | 358 | options.app = "%s-bin" % options.app |
michael@0 | 359 | |
michael@0 | 360 | options = MochitestOptions.verifyOptions(parser, options, mochitest) |
michael@0 | 361 | if options == None: |
michael@0 | 362 | sys.exit(1) |
michael@0 | 363 | |
michael@0 | 364 | if options.desktop and not options.profile: |
michael@0 | 365 | raise Exception("must specify --profile when specifying --desktop") |
michael@0 | 366 | |
michael@0 | 367 | options.browserArgs += ['-marionette'] |
michael@0 | 368 | |
michael@0 | 369 | sys.exit(mochitest.runTests(options, onLaunch=mochitest.startTests)) |
michael@0 | 370 | |
michael@0 | 371 | def main(): |
michael@0 | 372 | parser = B2GOptions() |
michael@0 | 373 | options, args = parser.parse_args() |
michael@0 | 374 | |
michael@0 | 375 | if options.desktop: |
michael@0 | 376 | run_desktop_mochitests(parser, options) |
michael@0 | 377 | else: |
michael@0 | 378 | run_remote_mochitests(parser, options) |
michael@0 | 379 | |
michael@0 | 380 | if __name__ == "__main__": |
michael@0 | 381 | main() |