Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | from __future__ import unicode_literals |
michael@0 | 6 | |
michael@0 | 7 | import logging |
michael@0 | 8 | import mozpack.path |
michael@0 | 9 | import os |
michael@0 | 10 | import platform |
michael@0 | 11 | import sys |
michael@0 | 12 | import warnings |
michael@0 | 13 | import which |
michael@0 | 14 | |
michael@0 | 15 | from mozbuild.base import ( |
michael@0 | 16 | MachCommandBase, |
michael@0 | 17 | MachCommandConditions as conditions, |
michael@0 | 18 | MozbuildObject, |
michael@0 | 19 | ) |
michael@0 | 20 | |
michael@0 | 21 | from mach.decorators import ( |
michael@0 | 22 | CommandArgument, |
michael@0 | 23 | CommandProvider, |
michael@0 | 24 | Command, |
michael@0 | 25 | ) |
michael@0 | 26 | |
michael@0 | 27 | from mach.logging import StructuredHumanFormatter |
michael@0 | 28 | |
michael@0 | 29 | ADB_NOT_FOUND = ''' |
michael@0 | 30 | The %s command requires the adb binary to be on your path. |
michael@0 | 31 | |
michael@0 | 32 | If you have a B2G build, this can be found in |
michael@0 | 33 | '%s/out/host/<platform>/bin'. |
michael@0 | 34 | '''.lstrip() |
michael@0 | 35 | |
michael@0 | 36 | GAIA_PROFILE_NOT_FOUND = ''' |
michael@0 | 37 | The %s command requires a non-debug gaia profile. Either pass in --profile, |
michael@0 | 38 | or set the GAIA_PROFILE environment variable. |
michael@0 | 39 | |
michael@0 | 40 | If you do not have a non-debug gaia profile, you can build one: |
michael@0 | 41 | $ git clone https://github.com/mozilla-b2g/gaia |
michael@0 | 42 | $ cd gaia |
michael@0 | 43 | $ make |
michael@0 | 44 | |
michael@0 | 45 | The profile should be generated in a directory called 'profile'. |
michael@0 | 46 | '''.lstrip() |
michael@0 | 47 | |
michael@0 | 48 | GAIA_PROFILE_IS_DEBUG = ''' |
michael@0 | 49 | The %s command requires a non-debug gaia profile. The specified profile, |
michael@0 | 50 | %s, is a debug profile. |
michael@0 | 51 | |
michael@0 | 52 | If you do not have a non-debug gaia profile, you can build one: |
michael@0 | 53 | $ git clone https://github.com/mozilla-b2g/gaia |
michael@0 | 54 | $ cd gaia |
michael@0 | 55 | $ make |
michael@0 | 56 | |
michael@0 | 57 | The profile should be generated in a directory called 'profile'. |
michael@0 | 58 | '''.lstrip() |
michael@0 | 59 | |
michael@0 | 60 | |
michael@0 | 61 | class UnexpectedFilter(logging.Filter): |
michael@0 | 62 | def filter(self, record): |
michael@0 | 63 | msg = getattr(record, 'params', {}).get('msg', '') |
michael@0 | 64 | return 'TEST-UNEXPECTED-' in msg |
michael@0 | 65 | |
michael@0 | 66 | |
michael@0 | 67 | class MochitestRunner(MozbuildObject): |
michael@0 | 68 | """Easily run mochitests. |
michael@0 | 69 | |
michael@0 | 70 | This currently contains just the basics for running mochitests. We may want |
michael@0 | 71 | to hook up result parsing, etc. |
michael@0 | 72 | """ |
michael@0 | 73 | |
michael@0 | 74 | def get_webapp_runtime_path(self): |
michael@0 | 75 | import mozinfo |
michael@0 | 76 | appname = 'webapprt-stub' + mozinfo.info.get('bin_suffix', '') |
michael@0 | 77 | if sys.platform.startswith('darwin'): |
michael@0 | 78 | appname = os.path.join(self.distdir, self.substs['MOZ_MACBUNDLE_NAME'], |
michael@0 | 79 | 'Contents', 'MacOS', appname) |
michael@0 | 80 | else: |
michael@0 | 81 | appname = os.path.join(self.distdir, 'bin', appname) |
michael@0 | 82 | return appname |
michael@0 | 83 | |
michael@0 | 84 | def __init__(self, *args, **kwargs): |
michael@0 | 85 | MozbuildObject.__init__(self, *args, **kwargs) |
michael@0 | 86 | |
michael@0 | 87 | # TODO Bug 794506 remove once mach integrates with virtualenv. |
michael@0 | 88 | build_path = os.path.join(self.topobjdir, 'build') |
michael@0 | 89 | if build_path not in sys.path: |
michael@0 | 90 | sys.path.append(build_path) |
michael@0 | 91 | |
michael@0 | 92 | self.tests_dir = os.path.join(self.topobjdir, '_tests') |
michael@0 | 93 | self.mochitest_dir = os.path.join(self.tests_dir, 'testing', 'mochitest') |
michael@0 | 94 | self.bin_dir = os.path.join(self.topobjdir, 'dist', 'bin') |
michael@0 | 95 | |
michael@0 | 96 | def run_b2g_test(self, test_paths=None, b2g_home=None, xre_path=None, |
michael@0 | 97 | total_chunks=None, this_chunk=None, no_window=None, |
michael@0 | 98 | **kwargs): |
michael@0 | 99 | """Runs a b2g mochitest. |
michael@0 | 100 | |
michael@0 | 101 | test_paths is an enumerable of paths to tests. It can be a relative path |
michael@0 | 102 | from the top source directory, an absolute filename, or a directory |
michael@0 | 103 | containing test files. |
michael@0 | 104 | """ |
michael@0 | 105 | # Need to call relpath before os.chdir() below. |
michael@0 | 106 | test_path = '' |
michael@0 | 107 | if test_paths: |
michael@0 | 108 | if len(test_paths) > 1: |
michael@0 | 109 | print('Warning: Only the first test path will be used.') |
michael@0 | 110 | test_path = self._wrap_path_argument(test_paths[0]).relpath() |
michael@0 | 111 | |
michael@0 | 112 | # TODO without os.chdir, chained imports fail below |
michael@0 | 113 | os.chdir(self.mochitest_dir) |
michael@0 | 114 | |
michael@0 | 115 | # The imp module can spew warnings if the modules below have |
michael@0 | 116 | # already been imported, ignore them. |
michael@0 | 117 | with warnings.catch_warnings(): |
michael@0 | 118 | warnings.simplefilter('ignore') |
michael@0 | 119 | |
michael@0 | 120 | import imp |
michael@0 | 121 | path = os.path.join(self.mochitest_dir, 'runtestsb2g.py') |
michael@0 | 122 | with open(path, 'r') as fh: |
michael@0 | 123 | imp.load_module('mochitest', fh, path, |
michael@0 | 124 | ('.py', 'r', imp.PY_SOURCE)) |
michael@0 | 125 | |
michael@0 | 126 | import mochitest |
michael@0 | 127 | from mochitest_options import B2GOptions |
michael@0 | 128 | |
michael@0 | 129 | parser = B2GOptions() |
michael@0 | 130 | options = parser.parse_args([])[0] |
michael@0 | 131 | |
michael@0 | 132 | test_path_dir = False; |
michael@0 | 133 | if test_path: |
michael@0 | 134 | test_root_file = mozpack.path.join(self.mochitest_dir, 'tests', test_path) |
michael@0 | 135 | if not os.path.exists(test_root_file): |
michael@0 | 136 | print('Specified test path does not exist: %s' % test_root_file) |
michael@0 | 137 | return 1 |
michael@0 | 138 | if os.path.isdir(test_root_file): |
michael@0 | 139 | test_path_dir = True; |
michael@0 | 140 | options.testPath = test_path |
michael@0 | 141 | |
michael@0 | 142 | for k, v in kwargs.iteritems(): |
michael@0 | 143 | setattr(options, k, v) |
michael@0 | 144 | options.noWindow = no_window |
michael@0 | 145 | options.totalChunks = total_chunks |
michael@0 | 146 | options.thisChunk = this_chunk |
michael@0 | 147 | |
michael@0 | 148 | options.symbolsPath = os.path.join(self.distdir, 'crashreporter-symbols') |
michael@0 | 149 | |
michael@0 | 150 | options.consoleLevel = 'INFO' |
michael@0 | 151 | if conditions.is_b2g_desktop(self): |
michael@0 | 152 | |
michael@0 | 153 | options.profile = options.profile or os.environ.get('GAIA_PROFILE') |
michael@0 | 154 | if not options.profile: |
michael@0 | 155 | print(GAIA_PROFILE_NOT_FOUND % 'mochitest-b2g-desktop') |
michael@0 | 156 | return 1 |
michael@0 | 157 | |
michael@0 | 158 | if os.path.isfile(os.path.join(options.profile, 'extensions', \ |
michael@0 | 159 | 'httpd@gaiamobile.org')): |
michael@0 | 160 | print(GAIA_PROFILE_IS_DEBUG % ('mochitest-b2g-desktop', |
michael@0 | 161 | options.profile)) |
michael@0 | 162 | return 1 |
michael@0 | 163 | |
michael@0 | 164 | options.desktop = True |
michael@0 | 165 | options.app = self.get_binary_path() |
michael@0 | 166 | if not options.app.endswith('-bin'): |
michael@0 | 167 | options.app = '%s-bin' % options.app |
michael@0 | 168 | if not os.path.isfile(options.app): |
michael@0 | 169 | options.app = options.app[:-len('-bin')] |
michael@0 | 170 | |
michael@0 | 171 | return mochitest.run_desktop_mochitests(parser, options) |
michael@0 | 172 | |
michael@0 | 173 | try: |
michael@0 | 174 | which.which('adb') |
michael@0 | 175 | except which.WhichError: |
michael@0 | 176 | # TODO Find adb automatically if it isn't on the path |
michael@0 | 177 | print(ADB_NOT_FOUND % ('mochitest-remote', b2g_home)) |
michael@0 | 178 | return 1 |
michael@0 | 179 | |
michael@0 | 180 | options.b2gPath = b2g_home |
michael@0 | 181 | options.logcat_dir = self.mochitest_dir |
michael@0 | 182 | options.httpdPath = self.mochitest_dir |
michael@0 | 183 | options.xrePath = xre_path |
michael@0 | 184 | return mochitest.run_remote_mochitests(parser, options) |
michael@0 | 185 | |
michael@0 | 186 | def run_desktop_test(self, context, suite=None, test_paths=None, debugger=None, |
michael@0 | 187 | debugger_args=None, slowscript=False, screenshot_on_fail = False, shuffle=False, keep_open=False, |
michael@0 | 188 | rerun_failures=False, no_autorun=False, repeat=0, run_until_failure=False, |
michael@0 | 189 | slow=False, chunk_by_dir=0, total_chunks=None, this_chunk=None, |
michael@0 | 190 | jsdebugger=False, debug_on_failure=False, start_at=None, end_at=None, |
michael@0 | 191 | e10s=False, dmd=False, dump_output_directory=None, |
michael@0 | 192 | dump_about_memory_after_test=False, dump_dmd_after_test=False, |
michael@0 | 193 | install_extension=None, quiet=False, environment=[], **kwargs): |
michael@0 | 194 | """Runs a mochitest. |
michael@0 | 195 | |
michael@0 | 196 | test_paths are path to tests. They can be a relative path from the |
michael@0 | 197 | top source directory, an absolute filename, or a directory containing |
michael@0 | 198 | test files. |
michael@0 | 199 | |
michael@0 | 200 | suite is the type of mochitest to run. It can be one of ('plain', |
michael@0 | 201 | 'chrome', 'browser', 'metro', 'a11y'). |
michael@0 | 202 | |
michael@0 | 203 | debugger is a program name or path to a binary (presumably a debugger) |
michael@0 | 204 | to run the test in. e.g. 'gdb' |
michael@0 | 205 | |
michael@0 | 206 | debugger_args are the arguments passed to the debugger. |
michael@0 | 207 | |
michael@0 | 208 | slowscript is true if the user has requested the SIGSEGV mechanism of |
michael@0 | 209 | invoking the slow script dialog. |
michael@0 | 210 | |
michael@0 | 211 | shuffle is whether test order should be shuffled (defaults to false). |
michael@0 | 212 | |
michael@0 | 213 | keep_open denotes whether to keep the browser open after tests |
michael@0 | 214 | complete. |
michael@0 | 215 | """ |
michael@0 | 216 | if rerun_failures and test_paths: |
michael@0 | 217 | print('Cannot specify both --rerun-failures and a test path.') |
michael@0 | 218 | return 1 |
michael@0 | 219 | |
michael@0 | 220 | # Need to call relpath before os.chdir() below. |
michael@0 | 221 | if test_paths: |
michael@0 | 222 | test_paths = [self._wrap_path_argument(p).relpath() for p in test_paths] |
michael@0 | 223 | |
michael@0 | 224 | failure_file_path = os.path.join(self.statedir, 'mochitest_failures.json') |
michael@0 | 225 | |
michael@0 | 226 | if rerun_failures and not os.path.exists(failure_file_path): |
michael@0 | 227 | print('No failure file present. Did you run mochitests before?') |
michael@0 | 228 | return 1 |
michael@0 | 229 | |
michael@0 | 230 | from StringIO import StringIO |
michael@0 | 231 | |
michael@0 | 232 | # runtests.py is ambiguous, so we load the file/module manually. |
michael@0 | 233 | if 'mochitest' not in sys.modules: |
michael@0 | 234 | import imp |
michael@0 | 235 | path = os.path.join(self.mochitest_dir, 'runtests.py') |
michael@0 | 236 | with open(path, 'r') as fh: |
michael@0 | 237 | imp.load_module('mochitest', fh, path, |
michael@0 | 238 | ('.py', 'r', imp.PY_SOURCE)) |
michael@0 | 239 | |
michael@0 | 240 | import mozinfo |
michael@0 | 241 | import mochitest |
michael@0 | 242 | from manifestparser import TestManifest |
michael@0 | 243 | from mozbuild.testing import TestResolver |
michael@0 | 244 | |
michael@0 | 245 | # This is required to make other components happy. Sad, isn't it? |
michael@0 | 246 | os.chdir(self.topobjdir) |
michael@0 | 247 | |
michael@0 | 248 | # Automation installs its own stream handler to stdout. Since we want |
michael@0 | 249 | # all logging to go through us, we just remove their handler. |
michael@0 | 250 | remove_handlers = [l for l in logging.getLogger().handlers |
michael@0 | 251 | if isinstance(l, logging.StreamHandler)] |
michael@0 | 252 | for handler in remove_handlers: |
michael@0 | 253 | logging.getLogger().removeHandler(handler) |
michael@0 | 254 | |
michael@0 | 255 | runner = mochitest.Mochitest() |
michael@0 | 256 | |
michael@0 | 257 | opts = mochitest.MochitestOptions() |
michael@0 | 258 | options, args = opts.parse_args([]) |
michael@0 | 259 | |
michael@0 | 260 | options.subsuite = '' |
michael@0 | 261 | flavor = suite |
michael@0 | 262 | |
michael@0 | 263 | # Need to set the suite options before verifyOptions below. |
michael@0 | 264 | if suite == 'plain': |
michael@0 | 265 | # Don't need additional options for plain. |
michael@0 | 266 | flavor = 'mochitest' |
michael@0 | 267 | elif suite == 'chrome': |
michael@0 | 268 | options.chrome = True |
michael@0 | 269 | elif suite == 'browser': |
michael@0 | 270 | options.browserChrome = True |
michael@0 | 271 | flavor = 'browser-chrome' |
michael@0 | 272 | elif suite == 'devtools': |
michael@0 | 273 | options.browserChrome = True |
michael@0 | 274 | options.subsuite = 'devtools' |
michael@0 | 275 | elif suite == 'metro': |
michael@0 | 276 | options.immersiveMode = True |
michael@0 | 277 | options.browserChrome = True |
michael@0 | 278 | elif suite == 'a11y': |
michael@0 | 279 | options.a11y = True |
michael@0 | 280 | elif suite == 'webapprt-content': |
michael@0 | 281 | options.webapprtContent = True |
michael@0 | 282 | options.app = self.get_webapp_runtime_path() |
michael@0 | 283 | elif suite == 'webapprt-chrome': |
michael@0 | 284 | options.webapprtChrome = True |
michael@0 | 285 | options.app = self.get_webapp_runtime_path() |
michael@0 | 286 | options.browserArgs.append("-test-mode") |
michael@0 | 287 | else: |
michael@0 | 288 | raise Exception('None or unrecognized mochitest suite type.') |
michael@0 | 289 | |
michael@0 | 290 | if dmd: |
michael@0 | 291 | options.dmdPath = self.bin_dir |
michael@0 | 292 | |
michael@0 | 293 | options.autorun = not no_autorun |
michael@0 | 294 | options.closeWhenDone = not keep_open |
michael@0 | 295 | options.slowscript = slowscript |
michael@0 | 296 | options.screenshotOnFail = screenshot_on_fail |
michael@0 | 297 | options.shuffle = shuffle |
michael@0 | 298 | options.consoleLevel = 'INFO' |
michael@0 | 299 | options.repeat = repeat |
michael@0 | 300 | options.runUntilFailure = run_until_failure |
michael@0 | 301 | options.runSlower = slow |
michael@0 | 302 | options.testingModulesDir = os.path.join(self.tests_dir, 'modules') |
michael@0 | 303 | options.extraProfileFiles.append(os.path.join(self.distdir, 'plugins')) |
michael@0 | 304 | options.symbolsPath = os.path.join(self.distdir, 'crashreporter-symbols') |
michael@0 | 305 | options.chunkByDir = chunk_by_dir |
michael@0 | 306 | options.totalChunks = total_chunks |
michael@0 | 307 | options.thisChunk = this_chunk |
michael@0 | 308 | options.jsdebugger = jsdebugger |
michael@0 | 309 | options.debugOnFailure = debug_on_failure |
michael@0 | 310 | options.startAt = start_at |
michael@0 | 311 | options.endAt = end_at |
michael@0 | 312 | options.e10s = e10s |
michael@0 | 313 | options.dumpAboutMemoryAfterTest = dump_about_memory_after_test |
michael@0 | 314 | options.dumpDMDAfterTest = dump_dmd_after_test |
michael@0 | 315 | options.dumpOutputDirectory = dump_output_directory |
michael@0 | 316 | options.quiet = quiet |
michael@0 | 317 | options.environment = environment |
michael@0 | 318 | |
michael@0 | 319 | options.failureFile = failure_file_path |
michael@0 | 320 | if install_extension != None: |
michael@0 | 321 | options.extensionsToInstall = [os.path.join(self.topsrcdir,install_extension)] |
michael@0 | 322 | |
michael@0 | 323 | for k, v in kwargs.iteritems(): |
michael@0 | 324 | setattr(options, k, v) |
michael@0 | 325 | |
michael@0 | 326 | if test_paths: |
michael@0 | 327 | resolver = self._spawn(TestResolver) |
michael@0 | 328 | |
michael@0 | 329 | tests = list(resolver.resolve_tests(paths=test_paths, flavor=flavor, |
michael@0 | 330 | cwd=context.cwd)) |
michael@0 | 331 | |
michael@0 | 332 | if not tests: |
michael@0 | 333 | print('No tests could be found in the path specified. Please ' |
michael@0 | 334 | 'specify a path that is a test file or is a directory ' |
michael@0 | 335 | 'containing tests.') |
michael@0 | 336 | return 1 |
michael@0 | 337 | |
michael@0 | 338 | manifest = TestManifest() |
michael@0 | 339 | manifest.tests.extend(tests) |
michael@0 | 340 | |
michael@0 | 341 | options.manifestFile = manifest |
michael@0 | 342 | |
michael@0 | 343 | if rerun_failures: |
michael@0 | 344 | options.testManifest = failure_file_path |
michael@0 | 345 | |
michael@0 | 346 | if debugger: |
michael@0 | 347 | options.debugger = debugger |
michael@0 | 348 | |
michael@0 | 349 | if debugger_args: |
michael@0 | 350 | if options.debugger == None: |
michael@0 | 351 | print("--debugger-args passed, but no debugger specified.") |
michael@0 | 352 | return 1 |
michael@0 | 353 | options.debuggerArgs = debugger_args |
michael@0 | 354 | |
michael@0 | 355 | options = opts.verifyOptions(options, runner) |
michael@0 | 356 | |
michael@0 | 357 | if options is None: |
michael@0 | 358 | raise Exception('mochitest option validator failed.') |
michael@0 | 359 | |
michael@0 | 360 | # We need this to enable colorization of output. |
michael@0 | 361 | self.log_manager.enable_unstructured() |
michael@0 | 362 | |
michael@0 | 363 | # Output processing is a little funky here. The old make targets |
michael@0 | 364 | # grepped the log output from TEST-UNEXPECTED-* and printed these lines |
michael@0 | 365 | # after test execution. Ideally the test runner would expose a Python |
michael@0 | 366 | # API for obtaining test results and we could just format failures |
michael@0 | 367 | # appropriately. Unfortunately, it doesn't yet do that. So, we capture |
michael@0 | 368 | # all output to a buffer then "grep" the buffer after test execution. |
michael@0 | 369 | # Bug 858197 tracks a Python API that would facilitate this. |
michael@0 | 370 | test_output = StringIO() |
michael@0 | 371 | handler = logging.StreamHandler(test_output) |
michael@0 | 372 | handler.addFilter(UnexpectedFilter()) |
michael@0 | 373 | handler.setFormatter(StructuredHumanFormatter(0, write_times=False)) |
michael@0 | 374 | logging.getLogger().addHandler(handler) |
michael@0 | 375 | |
michael@0 | 376 | result = runner.runTests(options) |
michael@0 | 377 | |
michael@0 | 378 | # Need to remove our buffering handler before we echo failures or else |
michael@0 | 379 | # it will catch them again! |
michael@0 | 380 | logging.getLogger().removeHandler(handler) |
michael@0 | 381 | self.log_manager.disable_unstructured() |
michael@0 | 382 | |
michael@0 | 383 | if test_output.getvalue(): |
michael@0 | 384 | result = 1 |
michael@0 | 385 | for line in test_output.getvalue().splitlines(): |
michael@0 | 386 | self.log(logging.INFO, 'unexpected', {'msg': line}, '{msg}') |
michael@0 | 387 | |
michael@0 | 388 | return result |
michael@0 | 389 | |
michael@0 | 390 | |
michael@0 | 391 | def MochitestCommand(func): |
michael@0 | 392 | """Decorator that adds shared command arguments to mochitest commands.""" |
michael@0 | 393 | |
michael@0 | 394 | # This employs light Python magic. Keep in mind a decorator is just a |
michael@0 | 395 | # function that takes a function, does something with it, then returns a |
michael@0 | 396 | # (modified) function. Here, we chain decorators onto the passed in |
michael@0 | 397 | # function. |
michael@0 | 398 | |
michael@0 | 399 | debugger = CommandArgument('--debugger', '-d', metavar='DEBUGGER', |
michael@0 | 400 | help='Debugger binary to run test in. Program name or path.') |
michael@0 | 401 | func = debugger(func) |
michael@0 | 402 | |
michael@0 | 403 | debugger_args = CommandArgument('--debugger-args', |
michael@0 | 404 | metavar='DEBUGGER_ARGS', help='Arguments to pass to the debugger.') |
michael@0 | 405 | func = debugger_args(func) |
michael@0 | 406 | |
michael@0 | 407 | # Bug 933807 introduced JS_DISABLE_SLOW_SCRIPT_SIGNALS to avoid clever |
michael@0 | 408 | # segfaults induced by the slow-script-detecting logic for Ion/Odin JITted |
michael@0 | 409 | # code. If we don't pass this, the user will need to periodically type |
michael@0 | 410 | # "continue" to (safely) resume execution. There are ways to implement |
michael@0 | 411 | # automatic resuming; see the bug. |
michael@0 | 412 | slowscript = CommandArgument('--slowscript', action='store_true', |
michael@0 | 413 | help='Do not set the JS_DISABLE_SLOW_SCRIPT_SIGNALS env variable; when not set, recoverable but misleading SIGSEGV instances may occur in Ion/Odin JIT code') |
michael@0 | 414 | func = slowscript(func) |
michael@0 | 415 | |
michael@0 | 416 | screenshot_on_fail = CommandArgument('--screenshot-on-fail', action='store_true', |
michael@0 | 417 | help='Take screenshots on all test failures. Set $MOZ_UPLOAD_DIR to a directory for storing the screenshots.') |
michael@0 | 418 | func = screenshot_on_fail(func) |
michael@0 | 419 | |
michael@0 | 420 | shuffle = CommandArgument('--shuffle', action='store_true', |
michael@0 | 421 | help='Shuffle execution order.') |
michael@0 | 422 | func = shuffle(func) |
michael@0 | 423 | |
michael@0 | 424 | keep_open = CommandArgument('--keep-open', action='store_true', |
michael@0 | 425 | help='Keep the browser open after tests complete.') |
michael@0 | 426 | func = keep_open(func) |
michael@0 | 427 | |
michael@0 | 428 | rerun = CommandArgument('--rerun-failures', action='store_true', |
michael@0 | 429 | help='Run only the tests that failed during the last test run.') |
michael@0 | 430 | func = rerun(func) |
michael@0 | 431 | |
michael@0 | 432 | autorun = CommandArgument('--no-autorun', action='store_true', |
michael@0 | 433 | help='Do not starting running tests automatically.') |
michael@0 | 434 | func = autorun(func) |
michael@0 | 435 | |
michael@0 | 436 | repeat = CommandArgument('--repeat', type=int, default=0, |
michael@0 | 437 | help='Repeat the test the given number of times.') |
michael@0 | 438 | func = repeat(func) |
michael@0 | 439 | |
michael@0 | 440 | runUntilFailure = CommandArgument("--run-until-failure", action='store_true', |
michael@0 | 441 | help='Run tests repeatedly and stops on the first time a test fails. ' \ |
michael@0 | 442 | 'Default cap is 30 runs, which can be overwritten ' \ |
michael@0 | 443 | 'with the --repeat parameter.') |
michael@0 | 444 | func = runUntilFailure(func) |
michael@0 | 445 | |
michael@0 | 446 | slow = CommandArgument('--slow', action='store_true', |
michael@0 | 447 | help='Delay execution between tests.') |
michael@0 | 448 | func = slow(func) |
michael@0 | 449 | |
michael@0 | 450 | end_at = CommandArgument('--end-at', type=str, |
michael@0 | 451 | help='Stop running the test sequence at this test.') |
michael@0 | 452 | func = end_at(func) |
michael@0 | 453 | |
michael@0 | 454 | start_at = CommandArgument('--start-at', type=str, |
michael@0 | 455 | help='Start running the test sequence at this test.') |
michael@0 | 456 | func = start_at(func) |
michael@0 | 457 | |
michael@0 | 458 | chunk_dir = CommandArgument('--chunk-by-dir', type=int, |
michael@0 | 459 | help='Group tests together in chunks by this many top directories.') |
michael@0 | 460 | func = chunk_dir(func) |
michael@0 | 461 | |
michael@0 | 462 | chunk_total = CommandArgument('--total-chunks', type=int, |
michael@0 | 463 | help='Total number of chunks to split tests into.') |
michael@0 | 464 | func = chunk_total(func) |
michael@0 | 465 | |
michael@0 | 466 | this_chunk = CommandArgument('--this-chunk', type=int, |
michael@0 | 467 | help='If running tests by chunks, the number of the chunk to run.') |
michael@0 | 468 | func = this_chunk(func) |
michael@0 | 469 | |
michael@0 | 470 | hide_subtests = CommandArgument('--hide-subtests', action='store_true', |
michael@0 | 471 | help='If specified, will only log subtest results on failure or timeout.') |
michael@0 | 472 | func = hide_subtests(func) |
michael@0 | 473 | |
michael@0 | 474 | debug_on_failure = CommandArgument('--debug-on-failure', action='store_true', |
michael@0 | 475 | help='Breaks execution and enters the JS debugger on a test failure. ' \ |
michael@0 | 476 | 'Should be used together with --jsdebugger.') |
michael@0 | 477 | func = debug_on_failure(func) |
michael@0 | 478 | |
michael@0 | 479 | jsdebugger = CommandArgument('--jsdebugger', action='store_true', |
michael@0 | 480 | help='Start the browser JS debugger before running the test. Implies --no-autorun.') |
michael@0 | 481 | func = jsdebugger(func) |
michael@0 | 482 | |
michael@0 | 483 | this_chunk = CommandArgument('--e10s', action='store_true', |
michael@0 | 484 | help='Run tests with electrolysis preferences and test filtering enabled.') |
michael@0 | 485 | func = this_chunk(func) |
michael@0 | 486 | |
michael@0 | 487 | dmd = CommandArgument('--dmd', action='store_true', |
michael@0 | 488 | help='Run tests with DMD active.') |
michael@0 | 489 | func = dmd(func) |
michael@0 | 490 | |
michael@0 | 491 | dumpAboutMemory = CommandArgument('--dump-about-memory-after-test', action='store_true', |
michael@0 | 492 | help='Dump an about:memory log after every test.') |
michael@0 | 493 | func = dumpAboutMemory(func) |
michael@0 | 494 | |
michael@0 | 495 | dumpDMD = CommandArgument('--dump-dmd-after-test', action='store_true', |
michael@0 | 496 | help='Dump a DMD log after every test.') |
michael@0 | 497 | func = dumpDMD(func) |
michael@0 | 498 | |
michael@0 | 499 | dumpOutputDirectory = CommandArgument('--dump-output-directory', action='store', |
michael@0 | 500 | help='Specifies the directory in which to place dumped memory reports.') |
michael@0 | 501 | func = dumpOutputDirectory(func) |
michael@0 | 502 | |
michael@0 | 503 | path = CommandArgument('test_paths', default=None, nargs='*', |
michael@0 | 504 | metavar='TEST', |
michael@0 | 505 | help='Test to run. Can be specified as a single file, a ' \ |
michael@0 | 506 | 'directory, or omitted. If omitted, the entire test suite is ' \ |
michael@0 | 507 | 'executed.') |
michael@0 | 508 | func = path(func) |
michael@0 | 509 | |
michael@0 | 510 | install_extension = CommandArgument('--install-extension', |
michael@0 | 511 | help='Install given extension before running selected tests. ' \ |
michael@0 | 512 | 'Parameter is a path to xpi file.') |
michael@0 | 513 | func = install_extension(func) |
michael@0 | 514 | |
michael@0 | 515 | quiet = CommandArgument('--quiet', default=False, action='store_true', |
michael@0 | 516 | help='Do not print test log lines unless a failure occurs.') |
michael@0 | 517 | func = quiet(func) |
michael@0 | 518 | |
michael@0 | 519 | setenv = CommandArgument('--setenv', default=[], action='append', |
michael@0 | 520 | metavar='NAME=VALUE', dest='environment', |
michael@0 | 521 | help="Sets the given variable in the application's environment") |
michael@0 | 522 | func = setenv(func) |
michael@0 | 523 | |
michael@0 | 524 | return func |
michael@0 | 525 | |
michael@0 | 526 | def B2GCommand(func): |
michael@0 | 527 | """Decorator that adds shared command arguments to b2g mochitest commands.""" |
michael@0 | 528 | |
michael@0 | 529 | busybox = CommandArgument('--busybox', default=None, |
michael@0 | 530 | help='Path to busybox binary to install on device') |
michael@0 | 531 | func = busybox(func) |
michael@0 | 532 | |
michael@0 | 533 | logcatdir = CommandArgument('--logcat-dir', default=None, |
michael@0 | 534 | help='directory to store logcat dump files') |
michael@0 | 535 | func = logcatdir(func) |
michael@0 | 536 | |
michael@0 | 537 | profile = CommandArgument('--profile', default=None, |
michael@0 | 538 | help='for desktop testing, the path to the \ |
michael@0 | 539 | gaia profile to use') |
michael@0 | 540 | func = profile(func) |
michael@0 | 541 | |
michael@0 | 542 | geckopath = CommandArgument('--gecko-path', default=None, |
michael@0 | 543 | help='the path to a gecko distribution that should \ |
michael@0 | 544 | be installed on the emulator prior to test') |
michael@0 | 545 | func = geckopath(func) |
michael@0 | 546 | |
michael@0 | 547 | nowindow = CommandArgument('--no-window', action='store_true', default=False, |
michael@0 | 548 | help='Pass --no-window to the emulator') |
michael@0 | 549 | func = nowindow(func) |
michael@0 | 550 | |
michael@0 | 551 | sdcard = CommandArgument('--sdcard', default="10MB", |
michael@0 | 552 | help='Define size of sdcard: 1MB, 50MB...etc') |
michael@0 | 553 | func = sdcard(func) |
michael@0 | 554 | |
michael@0 | 555 | emulator = CommandArgument('--emulator', default='arm', |
michael@0 | 556 | help='Architecture of emulator to use: x86 or arm') |
michael@0 | 557 | func = emulator(func) |
michael@0 | 558 | |
michael@0 | 559 | marionette = CommandArgument('--marionette', default=None, |
michael@0 | 560 | help='host:port to use when connecting to Marionette') |
michael@0 | 561 | func = marionette(func) |
michael@0 | 562 | |
michael@0 | 563 | chunk_total = CommandArgument('--total-chunks', type=int, |
michael@0 | 564 | help='Total number of chunks to split tests into.') |
michael@0 | 565 | func = chunk_total(func) |
michael@0 | 566 | |
michael@0 | 567 | this_chunk = CommandArgument('--this-chunk', type=int, |
michael@0 | 568 | help='If running tests by chunks, the number of the chunk to run.') |
michael@0 | 569 | func = this_chunk(func) |
michael@0 | 570 | |
michael@0 | 571 | hide_subtests = CommandArgument('--hide-subtests', action='store_true', |
michael@0 | 572 | help='If specified, will only log subtest results on failure or timeout.') |
michael@0 | 573 | func = hide_subtests(func) |
michael@0 | 574 | |
michael@0 | 575 | path = CommandArgument('test_paths', default=None, nargs='*', |
michael@0 | 576 | metavar='TEST', |
michael@0 | 577 | help='Test to run. Can be specified as a single file, a ' \ |
michael@0 | 578 | 'directory, or omitted. If omitted, the entire test suite is ' \ |
michael@0 | 579 | 'executed.') |
michael@0 | 580 | func = path(func) |
michael@0 | 581 | |
michael@0 | 582 | return func |
michael@0 | 583 | |
michael@0 | 584 | |
michael@0 | 585 | |
michael@0 | 586 | @CommandProvider |
michael@0 | 587 | class MachCommands(MachCommandBase): |
michael@0 | 588 | @Command('mochitest-plain', category='testing', |
michael@0 | 589 | conditions=[conditions.is_firefox], |
michael@0 | 590 | description='Run a plain mochitest.') |
michael@0 | 591 | @MochitestCommand |
michael@0 | 592 | def run_mochitest_plain(self, test_paths, **kwargs): |
michael@0 | 593 | return self.run_mochitest(test_paths, 'plain', **kwargs) |
michael@0 | 594 | |
michael@0 | 595 | @Command('mochitest-chrome', category='testing', |
michael@0 | 596 | conditions=[conditions.is_firefox], |
michael@0 | 597 | description='Run a chrome mochitest.') |
michael@0 | 598 | @MochitestCommand |
michael@0 | 599 | def run_mochitest_chrome(self, test_paths, **kwargs): |
michael@0 | 600 | return self.run_mochitest(test_paths, 'chrome', **kwargs) |
michael@0 | 601 | |
michael@0 | 602 | @Command('mochitest-browser', category='testing', |
michael@0 | 603 | conditions=[conditions.is_firefox], |
michael@0 | 604 | description='Run a mochitest with browser chrome.') |
michael@0 | 605 | @MochitestCommand |
michael@0 | 606 | def run_mochitest_browser(self, test_paths, **kwargs): |
michael@0 | 607 | return self.run_mochitest(test_paths, 'browser', **kwargs) |
michael@0 | 608 | |
michael@0 | 609 | @Command('mochitest-devtools', category='testing', |
michael@0 | 610 | conditions=[conditions.is_firefox], |
michael@0 | 611 | description='Run a devtools mochitest with browser chrome.') |
michael@0 | 612 | @MochitestCommand |
michael@0 | 613 | def run_mochitest_devtools(self, test_paths, **kwargs): |
michael@0 | 614 | return self.run_mochitest(test_paths, 'devtools', **kwargs) |
michael@0 | 615 | |
michael@0 | 616 | @Command('mochitest-metro', category='testing', |
michael@0 | 617 | conditions=[conditions.is_firefox], |
michael@0 | 618 | description='Run a mochitest with metro browser chrome.') |
michael@0 | 619 | @MochitestCommand |
michael@0 | 620 | def run_mochitest_metro(self, test_paths, **kwargs): |
michael@0 | 621 | return self.run_mochitest(test_paths, 'metro', **kwargs) |
michael@0 | 622 | |
michael@0 | 623 | @Command('mochitest-a11y', category='testing', |
michael@0 | 624 | conditions=[conditions.is_firefox], |
michael@0 | 625 | description='Run an a11y mochitest.') |
michael@0 | 626 | @MochitestCommand |
michael@0 | 627 | def run_mochitest_a11y(self, test_paths, **kwargs): |
michael@0 | 628 | return self.run_mochitest(test_paths, 'a11y', **kwargs) |
michael@0 | 629 | |
michael@0 | 630 | @Command('webapprt-test-chrome', category='testing', |
michael@0 | 631 | conditions=[conditions.is_firefox], |
michael@0 | 632 | description='Run a webapprt chrome mochitest.') |
michael@0 | 633 | @MochitestCommand |
michael@0 | 634 | def run_mochitest_webapprt_chrome(self, test_paths, **kwargs): |
michael@0 | 635 | return self.run_mochitest(test_paths, 'webapprt-chrome', **kwargs) |
michael@0 | 636 | |
michael@0 | 637 | @Command('webapprt-test-content', category='testing', |
michael@0 | 638 | conditions=[conditions.is_firefox], |
michael@0 | 639 | description='Run a webapprt content mochitest.') |
michael@0 | 640 | @MochitestCommand |
michael@0 | 641 | def run_mochitest_webapprt_content(self, test_paths, **kwargs): |
michael@0 | 642 | return self.run_mochitest(test_paths, 'webapprt-content', **kwargs) |
michael@0 | 643 | |
michael@0 | 644 | def run_mochitest(self, test_paths, flavor, **kwargs): |
michael@0 | 645 | from mozbuild.controller.building import BuildDriver |
michael@0 | 646 | |
michael@0 | 647 | self._ensure_state_subdir_exists('.') |
michael@0 | 648 | |
michael@0 | 649 | driver = self._spawn(BuildDriver) |
michael@0 | 650 | driver.install_tests(remove=False) |
michael@0 | 651 | |
michael@0 | 652 | mochitest = self._spawn(MochitestRunner) |
michael@0 | 653 | |
michael@0 | 654 | return mochitest.run_desktop_test(self._mach_context, |
michael@0 | 655 | test_paths=test_paths, suite=flavor, **kwargs) |
michael@0 | 656 | |
michael@0 | 657 | |
michael@0 | 658 | # TODO For now b2g commands will only work with the emulator, |
michael@0 | 659 | # they should be modified to work with all devices. |
michael@0 | 660 | def is_emulator(cls): |
michael@0 | 661 | """Emulator needs to be configured.""" |
michael@0 | 662 | return cls.device_name.find('emulator') == 0 |
michael@0 | 663 | |
michael@0 | 664 | |
michael@0 | 665 | @CommandProvider |
michael@0 | 666 | class B2GCommands(MachCommandBase): |
michael@0 | 667 | """So far these are only mochitest plain. They are |
michael@0 | 668 | implemented separately because their command lines |
michael@0 | 669 | are completely different. |
michael@0 | 670 | """ |
michael@0 | 671 | def __init__(self, context): |
michael@0 | 672 | MachCommandBase.__init__(self, context) |
michael@0 | 673 | |
michael@0 | 674 | for attr in ('b2g_home', 'xre_path', 'device_name'): |
michael@0 | 675 | setattr(self, attr, getattr(context, attr, None)) |
michael@0 | 676 | |
michael@0 | 677 | @Command('mochitest-remote', category='testing', |
michael@0 | 678 | description='Run a remote mochitest.', |
michael@0 | 679 | conditions=[conditions.is_b2g, is_emulator]) |
michael@0 | 680 | @B2GCommand |
michael@0 | 681 | def run_mochitest_remote(self, test_paths, **kwargs): |
michael@0 | 682 | from mozbuild.controller.building import BuildDriver |
michael@0 | 683 | |
michael@0 | 684 | self._ensure_state_subdir_exists('.') |
michael@0 | 685 | |
michael@0 | 686 | driver = self._spawn(BuildDriver) |
michael@0 | 687 | driver.install_tests(remove=False) |
michael@0 | 688 | |
michael@0 | 689 | mochitest = self._spawn(MochitestRunner) |
michael@0 | 690 | return mochitest.run_b2g_test(b2g_home=self.b2g_home, |
michael@0 | 691 | xre_path=self.xre_path, test_paths=test_paths, **kwargs) |
michael@0 | 692 | |
michael@0 | 693 | @Command('mochitest-b2g-desktop', category='testing', |
michael@0 | 694 | conditions=[conditions.is_b2g_desktop], |
michael@0 | 695 | description='Run a b2g desktop mochitest.') |
michael@0 | 696 | @B2GCommand |
michael@0 | 697 | def run_mochitest_b2g_desktop(self, test_paths, **kwargs): |
michael@0 | 698 | from mozbuild.controller.building import BuildDriver |
michael@0 | 699 | |
michael@0 | 700 | self._ensure_state_subdir_exists('.') |
michael@0 | 701 | |
michael@0 | 702 | driver = self._spawn(BuildDriver) |
michael@0 | 703 | driver.install_tests(remove=False) |
michael@0 | 704 | |
michael@0 | 705 | mochitest = self._spawn(MochitestRunner) |
michael@0 | 706 | return mochitest.run_b2g_test(test_paths=test_paths, **kwargs) |