2012-09-26 09:43:53 -07:00
# This Source Code Form is subject to the terms of the Mozilla Public
2012-10-10 11:08:09 -07:00
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
2012-09-26 09:43:53 -07:00
from __future__ import unicode_literals
2014-08-13 09:03:00 -07:00
import argparse
2013-04-04 15:17:23 -07:00
import logging
2013-03-28 09:23:03 -07:00
import mozpack . path
2012-10-02 14:03:31 -07:00
import os
2013-03-26 15:00:43 -07:00
import sys
2013-09-19 11:43:44 -07:00
import warnings
2013-08-29 11:23:07 -07:00
import which
2012-10-02 14:03:31 -07:00
2012-11-06 16:57:41 -08:00
from mozbuild . base import (
MachCommandBase ,
2013-08-28 07:08:50 -07:00
MachCommandConditions as conditions ,
2012-11-06 16:57:41 -08:00
MozbuildObject ,
)
2012-10-10 11:08:09 -07:00
2012-11-06 16:57:41 -08:00
from mach . decorators import (
2012-10-10 11:08:09 -07:00
CommandArgument ,
CommandProvider ,
Command ,
)
2013-04-04 15:17:23 -07:00
2014-08-13 09:03:00 -07:00
from mozlog import structured
2013-09-19 11:43:44 -07:00
ADB_NOT_FOUND = '''
The % s command requires the adb binary to be on your path .
If you have a B2G build , this can be found in
' %s /out/host/<platform>/bin ' .
''' .lstrip()
GAIA_PROFILE_NOT_FOUND = '''
The % s command requires a non - debug gaia profile . Either pass in - - profile ,
or set the GAIA_PROFILE environment variable .
If you do not have a non - debug gaia profile , you can build one :
$ git clone https : / / github . com / mozilla - b2g / gaia
$ cd gaia
$ make
The profile should be generated in a directory called ' profile ' .
''' .lstrip()
GAIA_PROFILE_IS_DEBUG = '''
The % s command requires a non - debug gaia profile . The specified profile ,
% s , is a debug profile .
If you do not have a non - debug gaia profile , you can build one :
$ git clone https : / / github . com / mozilla - b2g / gaia
$ cd gaia
$ make
The profile should be generated in a directory called ' profile ' .
''' .lstrip()
2014-07-17 10:38:20 -07:00
ENG_BUILD_REQUIRED = '''
The % s command requires an engineering build . It may be the case that
VARIANT = user or PRODUCTION = 1 were set . Try re - building with VARIANT = eng :
$ VARIANT = eng . / build . sh
There should be an app called ' test-container.gaiamobile.org ' located in
% s .
''' .lstrip()
2014-06-17 15:42:08 -07:00
# Maps test flavors to mochitest suite type.
FLAVORS = {
' mochitest ' : ' plain ' ,
' chrome ' : ' chrome ' ,
' browser-chrome ' : ' browser ' ,
2014-09-22 11:08:06 -07:00
' jetpack-package ' : ' jetpack-package ' ,
' jetpack-addon ' : ' jetpack-addon ' ,
2014-06-17 15:42:08 -07:00
' a11y ' : ' a11y ' ,
' webapprt-chrome ' : ' webapprt-chrome ' ,
}
2013-04-04 15:17:23 -07:00
2012-10-10 11:08:09 -07:00
2012-10-10 11:08:09 -07:00
class MochitestRunner ( MozbuildObject ) :
2012-09-26 09:43:53 -07:00
""" Easily run mochitests.
This currently contains just the basics for running mochitests . We may want
to hook up result parsing , etc .
"""
2013-08-28 07:08:50 -07:00
2013-09-23 07:47:48 -07:00
def get_webapp_runtime_path ( self ) :
2013-09-24 13:44:23 -07:00
import mozinfo
2013-09-23 07:47:48 -07:00
appname = ' webapprt-stub ' + mozinfo . info . get ( ' bin_suffix ' , ' ' )
if sys . platform . startswith ( ' darwin ' ) :
appname = os . path . join ( self . distdir , self . substs [ ' MOZ_MACBUNDLE_NAME ' ] ,
' Contents ' , ' MacOS ' , appname )
else :
appname = os . path . join ( self . distdir , ' bin ' , appname )
return appname
2013-08-28 07:08:50 -07:00
def __init__ ( self , * args , * * kwargs ) :
MozbuildObject . __init__ ( self , * args , * * kwargs )
# TODO Bug 794506 remove once mach integrates with virtualenv.
build_path = os . path . join ( self . topobjdir , ' build ' )
if build_path not in sys . path :
sys . path . append ( build_path )
self . tests_dir = os . path . join ( self . topobjdir , ' _tests ' )
self . mochitest_dir = os . path . join ( self . tests_dir , ' testing ' , ' mochitest ' )
2014-01-16 09:23:32 -08:00
self . bin_dir = os . path . join ( self . topobjdir , ' dist ' , ' bin ' )
2013-08-28 07:08:50 -07:00
2014-03-24 16:19:57 -07:00
def run_b2g_test ( self , test_paths = None , b2g_home = None , xre_path = None ,
2013-11-25 06:12:50 -08:00
total_chunks = None , this_chunk = None , no_window = None ,
2014-08-04 20:29:00 -07:00
repeat = 0 , run_until_failure = False , * * kwargs ) :
2013-08-28 07:08:50 -07:00
""" Runs a b2g mochitest.
2014-03-24 16:19:57 -07:00
test_paths is an enumerable of paths to tests . It can be a relative path
from the top source directory , an absolute filename , or a directory
containing test files .
2013-08-28 07:08:50 -07:00
"""
2013-09-11 11:53:47 -07:00
# Need to call relpath before os.chdir() below.
test_path = ' '
2014-03-24 16:19:57 -07:00
if test_paths :
if len ( test_paths ) > 1 :
print ( ' Warning: Only the first test path will be used. ' )
test_path = self . _wrap_path_argument ( test_paths [ 0 ] ) . relpath ( )
2013-09-11 11:53:47 -07:00
2013-08-28 07:08:50 -07:00
# TODO without os.chdir, chained imports fail below
os . chdir ( self . mochitest_dir )
2013-09-19 11:43:44 -07:00
# The imp module can spew warnings if the modules below have
# already been imported, ignore them.
with warnings . catch_warnings ( ) :
warnings . simplefilter ( ' ignore ' )
2013-08-28 07:08:50 -07:00
2013-09-19 11:43:44 -07:00
import imp
path = os . path . join ( self . mochitest_dir , ' runtestsb2g.py ' )
with open ( path , ' r ' ) as fh :
imp . load_module ( ' mochitest ' , fh , path ,
( ' .py ' , ' r ' , imp . PY_SOURCE ) )
import mochitest
from mochitest_options import B2GOptions
2013-08-28 07:08:50 -07:00
parser = B2GOptions ( )
options = parser . parse_args ( [ ] ) [ 0 ]
2013-09-11 11:53:47 -07:00
if test_path :
test_root_file = mozpack . path . join ( self . mochitest_dir , ' tests ' , test_path )
if not os . path . exists ( test_root_file ) :
print ( ' Specified test path does not exist: %s ' % test_root_file )
return 1
options . testPath = test_path
2014-02-10 12:58:46 -08:00
2013-08-28 07:08:50 -07:00
for k , v in kwargs . iteritems ( ) :
setattr ( options , k , v )
2013-11-25 06:12:50 -08:00
options . noWindow = no_window
options . totalChunks = total_chunks
options . thisChunk = this_chunk
2014-08-04 20:29:00 -07:00
options . repeat = repeat
options . runUntilFailure = run_until_failure
2013-08-28 07:08:50 -07:00
2013-11-26 14:25:25 -08:00
options . symbolsPath = os . path . join ( self . distdir , ' crashreporter-symbols ' )
2013-09-19 11:43:44 -07:00
options . consoleLevel = ' INFO '
if conditions . is_b2g_desktop ( self ) :
options . desktop = True
options . app = self . get_binary_path ( )
if not options . app . endswith ( ' -bin ' ) :
options . app = ' %s -bin ' % options . app
if not os . path . isfile ( options . app ) :
options . app = options . app [ : - len ( ' -bin ' ) ]
return mochitest . run_desktop_mochitests ( parser , options )
try :
which . which ( ' adb ' )
except which . WhichError :
# TODO Find adb automatically if it isn't on the path
print ( ADB_NOT_FOUND % ( ' mochitest-remote ' , b2g_home ) )
return 1
options . b2gPath = b2g_home
2014-06-19 11:17:26 -07:00
options . logdir = self . mochitest_dir
2013-09-19 11:43:44 -07:00
options . httpdPath = self . mochitest_dir
options . xrePath = xre_path
return mochitest . run_remote_mochitests ( parser , options )
2013-08-28 07:08:50 -07:00
2014-03-24 16:19:57 -07:00
def run_desktop_test ( self , context , suite = None , test_paths = None , debugger = None ,
2014-09-11 07:29:57 -07:00
debugger_args = None , slowscript = False , screenshot_on_fail = False , shuffle = False , closure_behaviour = ' auto ' ,
2014-02-07 20:25:45 -08:00
rerun_failures = False , no_autorun = False , repeat = 0 , run_until_failure = False ,
2014-07-13 01:04:00 -07:00
slow = False , chunk_by_dir = 0 , total_chunks = None , this_chunk = None , extraPrefs = [ ] ,
2014-02-07 20:25:45 -08:00
jsdebugger = False , debug_on_failure = False , start_at = None , end_at = None ,
2014-09-16 00:20:07 -07:00
e10s = False , content_sandbox = ' off ' , dmd = False , dump_output_directory = None ,
2014-02-07 20:25:45 -08:00
dump_about_memory_after_test = False , dump_dmd_after_test = False ,
2014-07-04 04:55:00 -07:00
install_extension = None , quiet = False , environment = [ ] , app_override = None , bisectChunk = None , runByDir = False ,
2014-05-01 04:18:00 -07:00
useTestMediaDevices = False , * * kwargs ) :
2012-09-26 09:43:53 -07:00
""" Runs a mochitest.
2014-03-24 16:19:57 -07:00
test_paths are path to tests . They can be a relative path from the
2012-09-26 09:43:53 -07:00
top source directory , an absolute filename , or a directory containing
test files .
suite is the type of mochitest to run . It can be one of ( ' plain ' ,
2014-09-22 11:08:06 -07:00
' chrome ' , ' browser ' , ' metro ' , ' a11y ' , ' jetpack-package ' , ' jetpack-addon ' ) .
2012-12-05 14:27:54 -08:00
debugger is a program name or path to a binary ( presumably a debugger )
to run the test in . e . g . ' gdb '
2013-03-26 15:00:43 -07:00
2013-05-30 08:00:46 -07:00
debugger_args are the arguments passed to the debugger .
2014-02-07 20:25:45 -08:00
slowscript is true if the user has requested the SIGSEGV mechanism of
invoking the slow script dialog .
2013-03-26 15:00:43 -07:00
shuffle is whether test order should be shuffled ( defaults to false ) .
2014-09-11 07:29:57 -07:00
closure_behaviour denotes whether to keep the browser open after tests
2013-03-26 15:00:43 -07:00
complete .
2012-09-26 09:43:53 -07:00
"""
2014-03-24 16:19:57 -07:00
if rerun_failures and test_paths :
2013-03-26 15:00:43 -07:00
print ( ' Cannot specify both --rerun-failures and a test path. ' )
return 1
2014-09-03 04:06:00 -07:00
# Make absolute paths relative before calling os.chdir() below.
2014-03-24 16:19:57 -07:00
if test_paths :
2014-09-03 04:06:00 -07:00
test_paths = [ self . _wrap_path_argument ( p ) . relpath ( ) if os . path . isabs ( p ) else p for p in test_paths ]
2013-03-26 15:00:43 -07:00
failure_file_path = os . path . join ( self . statedir , ' mochitest_failures.json ' )
if rerun_failures and not os . path . exists ( failure_file_path ) :
print ( ' No failure file present. Did you run mochitests before? ' )
return 1
# runtests.py is ambiguous, so we load the file/module manually.
if ' mochitest ' not in sys . modules :
import imp
2013-08-28 07:08:50 -07:00
path = os . path . join ( self . mochitest_dir , ' runtests.py ' )
2013-03-26 15:00:43 -07:00
with open ( path , ' r ' ) as fh :
imp . load_module ( ' mochitest ' , fh , path ,
( ' .py ' , ' r ' , imp . PY_SOURCE ) )
import mochitest
2014-03-24 14:35:06 -07:00
from manifestparser import TestManifest
from mozbuild . testing import TestResolver
2013-03-26 15:00:43 -07:00
# This is required to make other components happy. Sad, isn't it?
os . chdir ( self . topobjdir )
2013-04-04 15:17:23 -07:00
# Automation installs its own stream handler to stdout. Since we want
# all logging to go through us, we just remove their handler.
remove_handlers = [ l for l in logging . getLogger ( ) . handlers
if isinstance ( l , logging . StreamHandler ) ]
for handler in remove_handlers :
logging . getLogger ( ) . removeHandler ( handler )
2013-09-23 07:47:48 -07:00
opts = mochitest . MochitestOptions ( )
2013-03-26 15:00:43 -07:00
options , args = opts . parse_args ( [ ] )
2014-03-25 09:52:53 -07:00
options . subsuite = ' '
2014-03-24 14:35:06 -07:00
flavor = suite
2013-08-23 07:06:16 -07:00
2013-03-28 09:23:03 -07:00
# Need to set the suite options before verifyOptions below.
if suite == ' plain ' :
# Don't need additional options for plain.
2014-03-24 14:35:06 -07:00
flavor = ' mochitest '
2013-03-28 09:23:03 -07:00
elif suite == ' chrome ' :
options . chrome = True
elif suite == ' browser ' :
options . browserChrome = True
2014-03-24 14:35:06 -07:00
flavor = ' browser-chrome '
2014-03-25 09:52:53 -07:00
elif suite == ' devtools ' :
options . browserChrome = True
options . subsuite = ' devtools '
2014-09-22 11:08:06 -07:00
elif suite == ' jetpack-package ' :
options . jetpackPackage = True
elif suite == ' jetpack-addon ' :
options . jetpackAddon = True
2013-03-28 09:23:03 -07:00
elif suite == ' metro ' :
options . immersiveMode = True
options . browserChrome = True
elif suite == ' a11y ' :
options . a11y = True
2013-08-23 07:06:16 -07:00
elif suite == ' webapprt-content ' :
options . webapprtContent = True
2013-09-23 07:47:48 -07:00
options . app = self . get_webapp_runtime_path ( )
2013-08-23 07:06:16 -07:00
elif suite == ' webapprt-chrome ' :
options . webapprtChrome = True
2013-09-23 07:47:48 -07:00
options . app = self . get_webapp_runtime_path ( )
2013-08-23 07:06:16 -07:00
options . browserArgs . append ( " -test-mode " )
2013-03-28 09:23:03 -07:00
else :
raise Exception ( ' None or unrecognized mochitest suite type. ' )
2013-11-13 11:48:29 -08:00
if dmd :
2014-01-16 09:23:32 -08:00
options . dmdPath = self . bin_dir
2013-11-13 11:48:29 -08:00
2013-03-26 15:00:43 -07:00
options . autorun = not no_autorun
2014-09-11 07:29:57 -07:00
options . closeWhenDone = closure_behaviour != ' open '
2014-02-07 20:25:45 -08:00
options . slowscript = slowscript
2014-04-11 19:23:00 -07:00
options . screenshotOnFail = screenshot_on_fail
2013-03-26 15:00:43 -07:00
options . shuffle = shuffle
options . consoleLevel = ' INFO '
options . repeat = repeat
2013-05-24 12:03:50 -07:00
options . runUntilFailure = run_until_failure
2013-03-26 15:00:43 -07:00
options . runSlower = slow
2013-08-28 07:08:50 -07:00
options . testingModulesDir = os . path . join ( self . tests_dir , ' modules ' )
2013-03-26 15:00:43 -07:00
options . extraProfileFiles . append ( os . path . join ( self . distdir , ' plugins ' ) )
options . symbolsPath = os . path . join ( self . distdir , ' crashreporter-symbols ' )
2013-09-09 12:36:03 -07:00
options . chunkByDir = chunk_by_dir
options . totalChunks = total_chunks
options . thisChunk = this_chunk
2013-10-21 09:12:12 -07:00
options . jsdebugger = jsdebugger
2013-11-01 12:23:34 -07:00
options . debugOnFailure = debug_on_failure
2013-10-28 12:24:55 -07:00
options . startAt = start_at
options . endAt = end_at
2013-11-03 15:07:49 -08:00
options . e10s = e10s
2014-09-16 00:20:07 -07:00
options . contentSandbox = content_sandbox
if options . contentSandbox != ' off ' :
options . e10s = True
2013-11-19 12:12:00 -08:00
options . dumpAboutMemoryAfterTest = dump_about_memory_after_test
options . dumpDMDAfterTest = dump_dmd_after_test
options . dumpOutputDirectory = dump_output_directory
2014-03-07 08:42:07 -08:00
options . quiet = quiet
2014-04-22 06:09:47 -07:00
options . environment = environment
2014-07-13 01:04:00 -07:00
options . extraPrefs = extraPrefs
2014-07-04 04:55:00 -07:00
options . bisectChunk = bisectChunk
2014-06-03 08:19:28 -07:00
options . runByDir = runByDir
2014-05-01 04:18:00 -07:00
options . useTestMediaDevices = useTestMediaDevices
2013-03-26 15:00:43 -07:00
options . failureFile = failure_file_path
2013-12-17 07:56:29 -08:00
if install_extension != None :
options . extensionsToInstall = [ os . path . join ( self . topsrcdir , install_extension ) ]
2013-03-26 15:00:43 -07:00
2014-01-10 07:45:52 -08:00
for k , v in kwargs . iteritems ( ) :
setattr ( options , k , v )
2014-03-24 16:19:57 -07:00
if test_paths :
2014-03-24 14:35:06 -07:00
resolver = self . _spawn ( TestResolver )
2014-07-01 14:21:00 -07:00
tests = list ( resolver . resolve_tests ( paths = test_paths , flavor = flavor ) )
2014-03-24 14:35:06 -07:00
if not tests :
print ( ' No tests could be found in the path specified. Please '
' specify a path that is a test file or is a directory '
' containing tests. ' )
2013-03-28 09:23:03 -07:00
return 1
2014-03-24 14:35:06 -07:00
manifest = TestManifest ( )
manifest . tests . extend ( tests )
2014-09-11 07:29:57 -07:00
if len ( tests ) == 1 and closure_behaviour == ' auto ' and suite == ' plain ' :
2014-05-22 12:09:21 -07:00
options . closeWhenDone = False
2014-03-24 14:35:06 -07:00
options . manifestFile = manifest
2012-09-26 09:43:53 -07:00
2013-03-26 15:00:43 -07:00
if rerun_failures :
options . testManifest = failure_file_path
2012-12-05 14:27:54 -08:00
if debugger :
2013-03-26 15:00:43 -07:00
options . debugger = debugger
2013-05-30 08:00:46 -07:00
if debugger_args :
if options . debugger == None :
print ( " --debugger-args passed, but no debugger specified. " )
return 1
options . debuggerArgs = debugger_args
2014-08-22 07:28:04 -07:00
if app_override :
if app_override == " dist " :
options . app = self . get_binary_path ( where = ' staged-package ' )
elif app_override :
options . app = app_override
if options . gmp_path is None :
# Need to fix the location of gmp_fake which might not be shipped in the binary
bin_path = self . get_binary_path ( )
2014-08-25 08:23:34 -07:00
options . gmp_path = os . path . join ( os . path . dirname ( bin_path ) , ' gmp-fake ' , ' 1.0 ' )
2014-09-23 15:04:49 -07:00
options . gmp_path + = os . pathsep
options . gmp_path + = os . path . join ( os . path . dirname ( bin_path ) , ' gmp-clearkey ' , ' 0.1 ' )
2014-08-22 07:28:04 -07:00
2014-05-01 03:05:36 -07:00
2014-08-13 09:03:00 -07:00
logger_options = { key : value for key , value in vars ( options ) . iteritems ( ) if key . startswith ( ' log ' ) }
runner = mochitest . Mochitest ( logger_options )
2013-05-24 12:03:50 -07:00
options = opts . verifyOptions ( options , runner )
if options is None :
raise Exception ( ' mochitest option validator failed. ' )
2013-04-04 15:17:23 -07:00
# We need this to enable colorization of output.
self . log_manager . enable_unstructured ( )
result = runner . runTests ( options )
self . log_manager . disable_unstructured ( )
2014-07-22 02:56:00 -07:00
if runner . message_logger . errors :
2013-05-23 10:13:54 -07:00
result = 1
2014-07-22 02:56:00 -07:00
runner . message_logger . logger . warning ( " The following tests failed: " )
for error in runner . message_logger . errors :
runner . message_logger . logger . log_raw ( error )
2013-04-04 15:17:23 -07:00
2014-07-29 05:11:00 -07:00
runner . message_logger . finish ( )
2013-04-04 15:17:23 -07:00
return result
2013-03-26 15:00:43 -07:00
def MochitestCommand ( func ) :
""" Decorator that adds shared command arguments to mochitest commands. """
2012-12-05 14:27:54 -08:00
2013-03-26 15:00:43 -07:00
# This employs light Python magic. Keep in mind a decorator is just a
# function that takes a function, does something with it, then returns a
# (modified) function. Here, we chain decorators onto the passed in
# function.
debugger = CommandArgument ( ' --debugger ' , ' -d ' , metavar = ' DEBUGGER ' ,
help = ' Debugger binary to run test in. Program name or path. ' )
func = debugger ( func )
2013-05-30 08:00:46 -07:00
debugger_args = CommandArgument ( ' --debugger-args ' ,
metavar = ' DEBUGGER_ARGS ' , help = ' Arguments to pass to the debugger. ' )
func = debugger_args ( func )
2014-02-07 20:25:45 -08:00
# Bug 933807 introduced JS_DISABLE_SLOW_SCRIPT_SIGNALS to avoid clever
# segfaults induced by the slow-script-detecting logic for Ion/Odin JITted
# code. If we don't pass this, the user will need to periodically type
# "continue" to (safely) resume execution. There are ways to implement
# automatic resuming; see the bug.
slowscript = CommandArgument ( ' --slowscript ' , action = ' store_true ' ,
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 ' )
func = slowscript ( func )
2014-04-11 19:23:00 -07:00
screenshot_on_fail = CommandArgument ( ' --screenshot-on-fail ' , action = ' store_true ' ,
help = ' Take screenshots on all test failures. Set $MOZ_UPLOAD_DIR to a directory for storing the screenshots. ' )
func = screenshot_on_fail ( func )
2013-03-26 15:00:43 -07:00
shuffle = CommandArgument ( ' --shuffle ' , action = ' store_true ' ,
help = ' Shuffle execution order. ' )
func = shuffle ( func )
2014-09-11 07:29:57 -07:00
keep_open = CommandArgument ( ' --keep-open ' , action = ' store_const ' ,
dest = ' closure_behaviour ' , const = ' open ' , default = ' auto ' ,
help = ' Always keep the browser open after tests complete. ' )
2013-03-26 15:00:43 -07:00
func = keep_open ( func )
2014-09-11 07:29:57 -07:00
autoclose = CommandArgument ( ' --auto-close ' , action = ' store_const ' ,
dest = ' closure_behaviour ' , const = ' close ' , default = ' auto ' ,
help = ' Always close the browser after tests complete. ' )
func = autoclose ( func )
2013-03-26 15:00:43 -07:00
rerun = CommandArgument ( ' --rerun-failures ' , action = ' store_true ' ,
2013-08-23 07:06:16 -07:00
help = ' Run only the tests that failed during the last test run. ' )
2013-03-26 15:00:43 -07:00
func = rerun ( func )
autorun = CommandArgument ( ' --no-autorun ' , action = ' store_true ' ,
help = ' Do not starting running tests automatically. ' )
func = autorun ( func )
repeat = CommandArgument ( ' --repeat ' , type = int , default = 0 ,
help = ' Repeat the test the given number of times. ' )
func = repeat ( func )
2013-05-24 12:03:50 -07:00
runUntilFailure = CommandArgument ( " --run-until-failure " , action = ' store_true ' ,
2013-11-05 07:48:36 -08:00
help = ' Run tests repeatedly and stops on the first time a test fails. ' \
' Default cap is 30 runs, which can be overwritten ' \
' with the --repeat parameter. ' )
2013-05-24 12:03:50 -07:00
func = runUntilFailure ( func )
2013-03-26 15:00:43 -07:00
slow = CommandArgument ( ' --slow ' , action = ' store_true ' ,
help = ' Delay execution between tests. ' )
func = slow ( func )
2013-11-01 12:23:32 -07:00
end_at = CommandArgument ( ' --end-at ' , type = str ,
help = ' Stop running the test sequence at this test. ' )
func = end_at ( func )
start_at = CommandArgument ( ' --start-at ' , type = str ,
help = ' Start running the test sequence at this test. ' )
func = start_at ( func )
2013-09-09 12:36:03 -07:00
chunk_dir = CommandArgument ( ' --chunk-by-dir ' , type = int ,
help = ' Group tests together in chunks by this many top directories. ' )
func = chunk_dir ( func )
chunk_total = CommandArgument ( ' --total-chunks ' , type = int ,
help = ' Total number of chunks to split tests into. ' )
func = chunk_total ( func )
this_chunk = CommandArgument ( ' --this-chunk ' , type = int ,
help = ' If running tests by chunks, the number of the chunk to run. ' )
func = this_chunk ( func )
2013-11-01 12:23:34 -07:00
debug_on_failure = CommandArgument ( ' --debug-on-failure ' , action = ' store_true ' ,
help = ' Breaks execution and enters the JS debugger on a test failure. ' \
' Should be used together with --jsdebugger. ' )
func = debug_on_failure ( func )
2014-07-13 01:04:00 -07:00
setpref = CommandArgument ( ' --setpref ' , default = [ ] , action = ' append ' ,
metavar = ' PREF=VALUE ' , dest = ' extraPrefs ' ,
help = ' defines an extra user preference ' )
func = setpref ( func )
2013-10-21 09:12:12 -07:00
jsdebugger = CommandArgument ( ' --jsdebugger ' , action = ' store_true ' ,
help = ' Start the browser JS debugger before running the test. Implies --no-autorun. ' )
func = jsdebugger ( func )
2013-11-03 15:07:49 -08:00
this_chunk = CommandArgument ( ' --e10s ' , action = ' store_true ' ,
help = ' Run tests with electrolysis preferences and test filtering enabled. ' )
func = this_chunk ( func )
2014-09-16 00:20:07 -07:00
this_chunk = CommandArgument ( ' --content-sandbox ' , default = ' off ' , choices = [ ' off ' , ' warn ' , ' on ' ] ,
help = ' Run tests with the content sandbox enabled or in warn only mode (Windows only). --e10s is assumed. ' )
func = this_chunk ( func )
2013-11-13 11:48:29 -08:00
dmd = CommandArgument ( ' --dmd ' , action = ' store_true ' ,
help = ' Run tests with DMD active. ' )
func = dmd ( func )
2013-11-19 12:12:00 -08:00
dumpAboutMemory = CommandArgument ( ' --dump-about-memory-after-test ' , action = ' store_true ' ,
help = ' Dump an about:memory log after every test. ' )
func = dumpAboutMemory ( func )
dumpDMD = CommandArgument ( ' --dump-dmd-after-test ' , action = ' store_true ' ,
help = ' Dump a DMD log after every test. ' )
func = dumpDMD ( func )
dumpOutputDirectory = CommandArgument ( ' --dump-output-directory ' , action = ' store ' ,
help = ' Specifies the directory in which to place dumped memory reports. ' )
func = dumpOutputDirectory ( func )
2014-03-24 16:19:57 -07:00
path = CommandArgument ( ' test_paths ' , default = None , nargs = ' * ' ,
2013-03-26 15:00:43 -07:00
metavar = ' TEST ' ,
help = ' Test to run. Can be specified as a single file, a ' \
' directory, or omitted. If omitted, the entire test suite is ' \
' executed. ' )
func = path ( func )
2013-12-17 07:56:29 -08:00
install_extension = CommandArgument ( ' --install-extension ' ,
help = ' Install given extension before running selected tests. ' \
' Parameter is a path to xpi file. ' )
func = install_extension ( func )
2014-03-07 08:42:07 -08:00
quiet = CommandArgument ( ' --quiet ' , default = False , action = ' store_true ' ,
help = ' Do not print test log lines unless a failure occurs. ' )
func = quiet ( func )
2014-04-22 06:09:47 -07:00
setenv = CommandArgument ( ' --setenv ' , default = [ ] , action = ' append ' ,
metavar = ' NAME=VALUE ' , dest = ' environment ' ,
help = " Sets the given variable in the application ' s environment " )
func = setenv ( func )
2014-06-03 08:19:28 -07:00
runbydir = CommandArgument ( ' --run-by-dir ' , default = False ,
action = ' store_true ' ,
dest = ' runByDir ' ,
help = ' Run each directory in a single browser instance with a fresh profile. ' )
func = runbydir ( func )
2014-07-04 04:55:00 -07:00
bisect_chunk = CommandArgument ( ' --bisect-chunk ' , type = str ,
dest = ' bisectChunk ' ,
help = ' Specify the failing test name to find the previous tests that may be causing the failure. ' )
func = bisect_chunk ( func )
2014-05-01 04:18:00 -07:00
test_media = CommandArgument ( ' --use-test-media-devices ' , default = False ,
action = ' store_true ' ,
dest = ' useTestMediaDevices ' ,
help = ' Use test media device drivers for media testing. ' )
func = test_media ( func )
2014-05-01 03:05:36 -07:00
app_override = CommandArgument ( ' --app-override ' , default = None , action = ' store ' ,
help = " Override the default binary used to run tests with the path you provide, e.g. " \
" --app-override /usr/bin/firefox . " \
" If you have run ./mach package beforehand, you can specify ' dist ' to " \
" run tests against the distribution bundle ' s binary. " ) ;
func = app_override ( func )
2013-03-26 15:00:43 -07:00
return func
2012-10-10 11:08:09 -07:00
2013-08-28 07:08:50 -07:00
def B2GCommand ( func ) :
""" Decorator that adds shared command arguments to b2g mochitest commands. """
busybox = CommandArgument ( ' --busybox ' , default = None ,
help = ' Path to busybox binary to install on device ' )
func = busybox ( func )
2014-06-19 11:17:26 -07:00
logdir = CommandArgument ( ' --logdir ' , default = None ,
help = ' directory to store log files ' )
func = logdir ( func )
2013-08-28 07:08:50 -07:00
profile = CommandArgument ( ' --profile ' , default = None ,
help = ' for desktop testing, the path to the \
gaia profile to use ' )
func = profile ( func )
geckopath = CommandArgument ( ' --gecko-path ' , default = None ,
help = ' the path to a gecko distribution that should \
be installed on the emulator prior to test ' )
func = geckopath ( func )
nowindow = CommandArgument ( ' --no-window ' , action = ' store_true ' , default = False ,
help = ' Pass --no-window to the emulator ' )
func = nowindow ( func )
sdcard = CommandArgument ( ' --sdcard ' , default = " 10MB " ,
help = ' Define size of sdcard: 1MB, 50MB...etc ' )
func = sdcard ( func )
marionette = CommandArgument ( ' --marionette ' , default = None ,
help = ' host:port to use when connecting to Marionette ' )
func = marionette ( func )
2013-09-19 11:43:44 -07:00
chunk_total = CommandArgument ( ' --total-chunks ' , type = int ,
help = ' Total number of chunks to split tests into. ' )
func = chunk_total ( func )
this_chunk = CommandArgument ( ' --this-chunk ' , type = int ,
help = ' If running tests by chunks, the number of the chunk to run. ' )
func = this_chunk ( func )
2014-03-24 16:19:57 -07:00
path = CommandArgument ( ' test_paths ' , default = None , nargs = ' * ' ,
2013-08-28 07:08:50 -07:00
metavar = ' TEST ' ,
help = ' Test to run. Can be specified as a single file, a ' \
' directory, or omitted. If omitted, the entire test suite is ' \
' executed. ' )
func = path ( func )
2014-08-04 20:29:00 -07:00
repeat = CommandArgument ( ' --repeat ' , type = int , default = 0 ,
help = ' Repeat the test the given number of times. ' )
func = repeat ( func )
runUntilFailure = CommandArgument ( " --run-until-failure " , action = ' store_true ' ,
help = ' Run tests repeatedly and stops on the first time a test fails. ' \
' Default cap is 30 runs, which can be overwritten ' \
' with the --repeat parameter. ' )
func = runUntilFailure ( func )
2013-08-28 07:08:50 -07:00
return func
2012-10-10 11:08:09 -07:00
2014-08-13 09:03:00 -07:00
_st_parser = argparse . ArgumentParser ( )
structured . commandline . add_logging_group ( _st_parser )
2013-09-19 11:43:44 -07:00
2012-10-10 11:08:09 -07:00
@CommandProvider
2012-11-06 16:57:41 -08:00
class MachCommands ( MachCommandBase ) :
2013-05-08 17:56:30 -07:00
@Command ( ' mochitest-plain ' , category = ' testing ' ,
2014-07-22 14:41:09 -07:00
conditions = [ conditions . is_firefox_or_mulet ] ,
2014-08-13 09:03:00 -07:00
description = ' Run a plain mochitest (integration test, plain web page). ' ,
parser = _st_parser )
2013-03-26 15:00:43 -07:00
@MochitestCommand
2014-03-24 16:19:57 -07:00
def run_mochitest_plain ( self , test_paths , * * kwargs ) :
return self . run_mochitest ( test_paths , ' plain ' , * * kwargs )
2012-10-10 11:08:09 -07:00
2013-05-08 17:56:30 -07:00
@Command ( ' mochitest-chrome ' , category = ' testing ' ,
2013-09-19 11:43:44 -07:00
conditions = [ conditions . is_firefox ] ,
2014-08-25 09:32:00 -07:00
description = ' Run a chrome mochitest (integration test with some XUL). ' ,
parser = _st_parser )
2013-03-26 15:00:43 -07:00
@MochitestCommand
2014-03-24 16:19:57 -07:00
def run_mochitest_chrome ( self , test_paths , * * kwargs ) :
return self . run_mochitest ( test_paths , ' chrome ' , * * kwargs )
2012-10-10 11:08:09 -07:00
2013-05-08 17:56:30 -07:00
@Command ( ' mochitest-browser ' , category = ' testing ' ,
2013-09-19 11:43:44 -07:00
conditions = [ conditions . is_firefox ] ,
2014-08-25 09:32:00 -07:00
description = ' Run a mochitest with browser chrome (integration test with a standard browser). ' ,
parser = _st_parser )
2013-03-26 15:00:43 -07:00
@MochitestCommand
2014-03-24 16:19:57 -07:00
def run_mochitest_browser ( self , test_paths , * * kwargs ) :
return self . run_mochitest ( test_paths , ' browser ' , * * kwargs )
2012-10-10 11:08:09 -07:00
2014-03-25 09:52:53 -07:00
@Command ( ' mochitest-devtools ' , category = ' testing ' ,
conditions = [ conditions . is_firefox ] ,
2014-08-25 09:32:00 -07:00
description = ' Run a devtools mochitest with browser chrome (integration test with a standard browser with the devtools frame). ' ,
parser = _st_parser )
2014-03-25 09:52:53 -07:00
@MochitestCommand
def run_mochitest_devtools ( self , test_paths , * * kwargs ) :
return self . run_mochitest ( test_paths , ' devtools ' , * * kwargs )
2014-09-22 11:08:06 -07:00
@Command ( ' jetpack-package ' , category = ' testing ' ,
conditions = [ conditions . is_firefox ] ,
description = ' Run a jetpack package test. ' )
@MochitestCommand
def run_mochitest_jetpack_package ( self , test_paths , * * kwargs ) :
return self . run_mochitest ( test_paths , ' jetpack-package ' , * * kwargs )
@Command ( ' jetpack-addon ' , category = ' testing ' ,
conditions = [ conditions . is_firefox ] ,
description = ' Run a jetpack addon test. ' )
@MochitestCommand
def run_mochitest_jetpack_addon ( self , test_paths , * * kwargs ) :
return self . run_mochitest ( test_paths , ' jetpack-addon ' , * * kwargs )
2013-05-08 17:56:30 -07:00
@Command ( ' mochitest-metro ' , category = ' testing ' ,
2013-09-19 11:43:44 -07:00
conditions = [ conditions . is_firefox ] ,
2014-08-25 09:32:00 -07:00
description = ' Run a mochitest with metro browser chrome (tests for Windows touch interface). ' ,
parser = _st_parser )
2013-03-26 15:00:43 -07:00
@MochitestCommand
2014-03-24 16:19:57 -07:00
def run_mochitest_metro ( self , test_paths , * * kwargs ) :
return self . run_mochitest ( test_paths , ' metro ' , * * kwargs )
2013-02-12 12:51:24 -08:00
2013-05-08 17:56:30 -07:00
@Command ( ' mochitest-a11y ' , category = ' testing ' ,
2013-09-19 11:43:44 -07:00
conditions = [ conditions . is_firefox ] ,
2014-08-25 09:32:00 -07:00
description = ' Run an a11y mochitest (accessibility tests). ' ,
parser = _st_parser )
2013-03-26 15:00:43 -07:00
@MochitestCommand
2014-03-24 16:19:57 -07:00
def run_mochitest_a11y ( self , test_paths , * * kwargs ) :
return self . run_mochitest ( test_paths , ' a11y ' , * * kwargs )
2013-03-26 15:00:43 -07:00
2013-08-23 07:06:16 -07:00
@Command ( ' webapprt-test-chrome ' , category = ' testing ' ,
2013-09-19 11:43:44 -07:00
conditions = [ conditions . is_firefox ] ,
2014-08-25 09:32:00 -07:00
description = ' Run a webapprt chrome mochitest (Web App Runtime with the browser chrome). ' ,
parser = _st_parser )
2013-08-23 07:06:16 -07:00
@MochitestCommand
2014-03-24 16:19:57 -07:00
def run_mochitest_webapprt_chrome ( self , test_paths , * * kwargs ) :
return self . run_mochitest ( test_paths , ' webapprt-chrome ' , * * kwargs )
2013-08-23 07:06:16 -07:00
@Command ( ' webapprt-test-content ' , category = ' testing ' ,
2013-09-19 11:43:44 -07:00
conditions = [ conditions . is_firefox ] ,
2014-08-25 09:32:00 -07:00
description = ' Run a webapprt content mochitest (Content rendering of the Web App Runtime). ' ,
parser = _st_parser )
2013-08-23 07:06:16 -07:00
@MochitestCommand
2014-03-24 16:19:57 -07:00
def run_mochitest_webapprt_content ( self , test_paths , * * kwargs ) :
return self . run_mochitest ( test_paths , ' webapprt-content ' , * * kwargs )
2013-08-23 07:06:16 -07:00
2014-06-17 15:42:08 -07:00
@Command ( ' mochitest ' , category = ' testing ' ,
conditions = [ conditions . is_firefox ] ,
2014-08-25 09:32:00 -07:00
description = ' Run any flavor of mochitest (integration test). ' ,
parser = _st_parser )
2014-06-17 15:42:08 -07:00
@MochitestCommand
2014-06-17 15:49:41 -07:00
@CommandArgument ( ' -f ' , ' --flavor ' , choices = FLAVORS . keys ( ) ,
help = ' Only run tests of this flavor. ' )
2014-06-18 11:19:45 -07:00
def run_mochitest_general ( self , test_paths , flavor = None , test_objects = None ,
* * kwargs ) :
2014-06-17 15:42:08 -07:00
self . _preruntest ( )
from mozbuild . testing import TestResolver
2014-06-18 11:19:45 -07:00
if test_objects :
tests = test_objects
else :
resolver = self . _spawn ( TestResolver )
tests = list ( resolver . resolve_tests ( paths = test_paths ,
cwd = self . _mach_context . cwd ) )
2014-06-17 15:42:08 -07:00
# Our current approach is to group the tests by suite and then perform
# an invocation for each suite. Ideally, this would be done
# automatically inside of core mochitest code. But it wasn't designed
# to do that.
#
# This does mean our output is less than ideal. When running tests from
# multiple suites, we see redundant summary lines. Hopefully once we
# have better machine readable output coming from mochitest land we can
# aggregate that here and improve the output formatting.
suites = { }
for test in tests :
2014-06-17 15:49:41 -07:00
# Filter out non-mochitests.
2014-06-17 15:42:08 -07:00
if test [ ' flavor ' ] not in FLAVORS :
continue
2014-06-17 15:49:41 -07:00
if flavor and test [ ' flavor ' ] != flavor :
continue
2014-06-17 15:42:08 -07:00
suite = FLAVORS [ test [ ' flavor ' ] ]
suites . setdefault ( suite , [ ] ) . append ( test )
mochitest = self . _spawn ( MochitestRunner )
overall = None
for suite , tests in sorted ( suites . items ( ) ) :
result = mochitest . run_desktop_test ( self . _mach_context ,
test_paths = [ test [ ' file_relpath ' ] for test in tests ] , suite = suite ,
* * kwargs )
if result :
overall = result
return overall
def _preruntest ( self ) :
2013-10-01 09:36:44 -07:00
from mozbuild . controller . building import BuildDriver
2012-11-02 10:32:40 -07:00
self . _ensure_state_subdir_exists ( ' . ' )
2013-10-01 09:36:44 -07:00
driver = self . _spawn ( BuildDriver )
driver . install_tests ( remove = False )
2014-06-17 15:42:08 -07:00
def run_mochitest ( self , test_paths , flavor , * * kwargs ) :
self . _preruntest ( )
2012-10-10 11:08:09 -07:00
mochitest = self . _spawn ( MochitestRunner )
2013-09-19 11:43:44 -07:00
2014-03-24 14:35:06 -07:00
return mochitest . run_desktop_test ( self . _mach_context ,
2014-03-24 16:19:57 -07:00
test_paths = test_paths , suite = flavor , * * kwargs )
2013-08-28 07:08:50 -07:00
# TODO For now b2g commands will only work with the emulator,
# they should be modified to work with all devices.
def is_emulator ( cls ) :
""" Emulator needs to be configured. """
2014-06-24 10:47:19 -07:00
try :
return cls . device_name . startswith ( ' emulator ' )
except AttributeError :
return False
2013-08-28 07:08:50 -07:00
@CommandProvider
class B2GCommands ( MachCommandBase ) :
2013-09-19 11:43:44 -07:00
""" So far these are only mochitest plain. They are
implemented separately because their command lines
are completely different .
"""
2013-08-28 07:08:50 -07:00
def __init__ ( self , context ) :
MachCommandBase . __init__ ( self , context )
2014-07-17 10:38:20 -07:00
for attr in ( ' b2g_home ' , ' xre_path ' , ' device_name ' , ' get_build_var ' ) :
2013-08-28 07:08:50 -07:00
setattr ( self , attr , getattr ( context , attr , None ) )
@Command ( ' mochitest-remote ' , category = ' testing ' ,
2014-08-09 06:37:29 -07:00
description = ' Run a remote mochitest (integration test for fennec/android). ' ,
2013-08-28 07:08:50 -07:00
conditions = [ conditions . is_b2g , is_emulator ] )
@B2GCommand
2014-03-24 16:19:57 -07:00
def run_mochitest_remote ( self , test_paths , * * kwargs ) :
2014-07-17 10:38:20 -07:00
if self . get_build_var :
host_webapps_dir = os . path . join ( self . get_build_var ( ' TARGET_OUT_DATA ' ) ,
' local ' , ' webapps ' )
if not os . path . isdir ( os . path . join ( host_webapps_dir ,
' test-container.gaiamobile.org ' ) ) :
print ( ENG_BUILD_REQUIRED % ( ' mochitest-remote ' , host_webapps_dir ) )
return 1
2013-10-01 09:36:44 -07:00
from mozbuild . controller . building import BuildDriver
2014-04-30 09:57:39 -07:00
if self . device_name . startswith ( ' emulator ' ) :
emulator = ' arm '
if ' x86 ' in self . device_name :
emulator = ' x86 '
kwargs [ ' emulator ' ] = emulator
2013-08-28 07:08:50 -07:00
self . _ensure_state_subdir_exists ( ' . ' )
2013-10-01 09:36:44 -07:00
driver = self . _spawn ( BuildDriver )
driver . install_tests ( remove = False )
2013-08-28 07:08:50 -07:00
mochitest = self . _spawn ( MochitestRunner )
2013-09-19 11:43:44 -07:00
return mochitest . run_b2g_test ( b2g_home = self . b2g_home ,
2014-03-24 16:19:57 -07:00
xre_path = self . xre_path , test_paths = test_paths , * * kwargs )
2013-09-19 11:43:44 -07:00
@Command ( ' mochitest-b2g-desktop ' , category = ' testing ' ,
conditions = [ conditions . is_b2g_desktop ] ,
2014-08-09 06:37:29 -07:00
description = ' Run a b2g desktop mochitest (same as mochitest-plain but for b2g desktop). ' )
2013-09-19 11:43:44 -07:00
@B2GCommand
2014-03-24 16:19:57 -07:00
def run_mochitest_b2g_desktop ( self , test_paths , * * kwargs ) :
2014-07-17 10:38:20 -07:00
kwargs [ ' profile ' ] = kwargs . get ( ' profile ' ) or os . environ . get ( ' GAIA_PROFILE ' )
if not kwargs [ ' profile ' ] or not os . path . isdir ( kwargs [ ' profile ' ] ) :
print ( GAIA_PROFILE_NOT_FOUND % ' mochitest-b2g-desktop ' )
return 1
if os . path . isfile ( os . path . join ( kwargs [ ' profile ' ] , ' extensions ' ,
' httpd@gaiamobile.org ' ) ) :
print ( GAIA_PROFILE_IS_DEBUG % ( ' mochitest-b2g-desktop ' ,
kwargs [ ' profile ' ] ) )
return 1
2013-10-01 09:36:44 -07:00
from mozbuild . controller . building import BuildDriver
2013-09-19 11:43:44 -07:00
self . _ensure_state_subdir_exists ( ' . ' )
2013-10-01 09:36:44 -07:00
driver = self . _spawn ( BuildDriver )
driver . install_tests ( remove = False )
2013-09-19 11:43:44 -07:00
mochitest = self . _spawn ( MochitestRunner )
2014-03-24 16:19:57 -07:00
return mochitest . run_b2g_test ( test_paths = test_paths , * * kwargs )
2014-09-08 16:23:12 -07:00
@CommandProvider
class AndroidCommands ( MachCommandBase ) :
@Command ( ' robocop ' , category = ' testing ' ,
conditions = [ conditions . is_android ] ,
description = ' Run a Robocop test. ' )
@CommandArgument ( ' test_path ' , default = None , nargs = ' ? ' ,
metavar = ' TEST ' ,
help = ' Test to run. Can be specified as a Robocop test name (like " testLoad " ), ' \
' or omitted. If omitted, the entire test suite is executed. ' )
def run_robocop ( self , test_path ) :
self . tests_dir = os . path . join ( self . topobjdir , ' _tests ' )
self . mochitest_dir = os . path . join ( self . tests_dir , ' testing ' , ' mochitest ' )
import imp
path = os . path . join ( self . mochitest_dir , ' runtestsremote.py ' )
with open ( path , ' r ' ) as fh :
imp . load_module ( ' runtestsremote ' , fh , path ,
( ' .py ' , ' r ' , imp . PY_SOURCE ) )
import runtestsremote
args = [
' --xre-path= ' + os . environ . get ( ' MOZ_HOST_BIN ' ) ,
' --dm_trans=adb ' ,
' --deviceIP= ' ,
' --console-level=INFO ' ,
' --app= ' + self . substs [ ' ANDROID_PACKAGE_NAME ' ] ,
' --robocop-apk= ' + os . path . join ( self . topobjdir , ' build ' , ' mobile ' , ' robocop ' , ' robocop-debug.apk ' ) ,
' --robocop-ini= ' + os . path . join ( self . topobjdir , ' build ' , ' mobile ' , ' robocop ' , ' robocop.ini ' ) ,
' --log-mach=- ' ,
]
if test_path :
args . append ( ' --test-path= %s ' % test_path )
sys . exit ( runtestsremote . main ( args ) )