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