summaryrefslogtreecommitdiffstats
path: root/testing/mach_commands.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mach_commands.py')
-rw-r--r--testing/mach_commands.py831
1 files changed, 831 insertions, 0 deletions
diff --git a/testing/mach_commands.py b/testing/mach_commands.py
new file mode 100644
index 000000000..866bc530c
--- /dev/null
+++ b/testing/mach_commands.py
@@ -0,0 +1,831 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# 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/.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import json
+import os
+import sys
+import tempfile
+import subprocess
+import shutil
+from collections import defaultdict
+
+from mach.decorators import (
+ CommandArgument,
+ CommandProvider,
+ Command,
+)
+
+from mozbuild.base import MachCommandBase
+from mozbuild.base import MachCommandConditions as conditions
+import mozpack.path as mozpath
+from argparse import ArgumentParser
+
+UNKNOWN_TEST = '''
+I was unable to find tests from the given argument(s).
+
+You should specify a test directory, filename, test suite name, or
+abbreviation. If no arguments are given, there must be local file
+changes and corresponding IMPACTED_TESTS annotations in moz.build
+files relevant to those files.
+
+It's possible my little brain doesn't know about the type of test you are
+trying to execute. If you suspect this, please request support by filing
+a bug at
+https://bugzilla.mozilla.org/enter_bug.cgi?product=Testing&component=General.
+'''.strip()
+
+UNKNOWN_FLAVOR = '''
+I know you are trying to run a %s test. Unfortunately, I can't run those
+tests yet. Sorry!
+'''.strip()
+
+MOCHITEST_CHUNK_BY_DIR = 4
+MOCHITEST_TOTAL_CHUNKS = 5
+
+TEST_SUITES = {
+ 'cppunittest': {
+ 'aliases': ('Cpp', 'cpp'),
+ 'mach_command': 'cppunittest',
+ 'kwargs': {'test_file': None},
+ },
+ 'crashtest': {
+ 'aliases': ('C', 'Rc', 'RC', 'rc'),
+ 'mach_command': 'crashtest',
+ 'kwargs': {'test_file': None},
+ },
+ 'firefox-ui-functional': {
+ 'aliases': ('Fxfn',),
+ 'mach_command': 'firefox-ui-functional',
+ 'kwargs': {},
+ },
+ 'firefox-ui-update': {
+ 'aliases': ('Fxup',),
+ 'mach_command': 'firefox-ui-update',
+ 'kwargs': {},
+ },
+ 'jetpack': {
+ 'aliases': ('J',),
+ 'mach_command': 'jetpack-test',
+ 'kwargs': {},
+ },
+ 'check-spidermonkey': {
+ 'aliases': ('Sm', 'sm'),
+ 'mach_command': 'check-spidermonkey',
+ 'kwargs': {'valgrind': False},
+ },
+ 'mochitest-a11y': {
+ 'mach_command': 'mochitest',
+ 'kwargs': {'flavor': 'a11y', 'test_paths': None},
+ },
+ 'mochitest-browser': {
+ 'aliases': ('bc', 'BC', 'Bc'),
+ 'mach_command': 'mochitest',
+ 'kwargs': {'flavor': 'browser-chrome', 'test_paths': None},
+ },
+ 'mochitest-chrome': {
+ 'mach_command': 'mochitest',
+ 'kwargs': {'flavor': 'chrome', 'test_paths': None},
+ },
+ 'mochitest-devtools': {
+ 'aliases': ('dt', 'DT', 'Dt'),
+ 'mach_command': 'mochitest',
+ 'kwargs': {'subsuite': 'devtools', 'test_paths': None},
+ },
+ 'mochitest-plain': {
+ 'mach_command': 'mochitest',
+ 'kwargs': {'flavor': 'plain', 'test_paths': None},
+ },
+ 'python': {
+ 'mach_command': 'python-test',
+ 'kwargs': {'tests': None},
+ },
+ 'reftest': {
+ 'aliases': ('RR', 'rr', 'Rr'),
+ 'mach_command': 'reftest',
+ 'kwargs': {'tests': None},
+ },
+ 'web-platform-tests': {
+ 'aliases': ('wpt',),
+ 'mach_command': 'web-platform-tests',
+ 'kwargs': {}
+ },
+ 'valgrind': {
+ 'aliases': ('V', 'v'),
+ 'mach_command': 'valgrind-test',
+ 'kwargs': {},
+ },
+ 'xpcshell': {
+ 'aliases': ('X', 'x'),
+ 'mach_command': 'xpcshell-test',
+ 'kwargs': {'test_file': 'all'},
+ },
+}
+
+# Maps test flavors to metadata on how to run that test.
+TEST_FLAVORS = {
+ 'a11y': {
+ 'mach_command': 'mochitest',
+ 'kwargs': {'flavor': 'a11y', 'test_paths': []},
+ },
+ 'browser-chrome': {
+ 'mach_command': 'mochitest',
+ 'kwargs': {'flavor': 'browser-chrome', 'test_paths': []},
+ },
+ 'crashtest': {},
+ 'chrome': {
+ 'mach_command': 'mochitest',
+ 'kwargs': {'flavor': 'chrome', 'test_paths': []},
+ },
+ 'firefox-ui-functional': {
+ 'mach_command': 'firefox-ui-functional',
+ 'kwargs': {'tests': []},
+ },
+ 'firefox-ui-update': {
+ 'mach_command': 'firefox-ui-update',
+ 'kwargs': {'tests': []},
+ },
+ 'marionette': {
+ 'mach_command': 'marionette-test',
+ 'kwargs': {'tests': []},
+ },
+ 'mochitest': {
+ 'mach_command': 'mochitest',
+ 'kwargs': {'flavor': 'mochitest', 'test_paths': []},
+ },
+ 'python': {
+ 'mach_command': 'python-test',
+ 'kwargs': {},
+ },
+ 'reftest': {
+ 'mach_command': 'reftest',
+ 'kwargs': {'tests': []}
+ },
+ 'steeplechase': {},
+ 'web-platform-tests': {
+ 'mach_command': 'web-platform-tests',
+ 'kwargs': {'include': []}
+ },
+ 'xpcshell': {
+ 'mach_command': 'xpcshell-test',
+ 'kwargs': {'test_paths': []},
+ },
+}
+
+for i in range(1, MOCHITEST_TOTAL_CHUNKS + 1):
+ TEST_SUITES['mochitest-%d' %i] = {
+ 'aliases': ('M%d' % i, 'm%d' % i),
+ 'mach_command': 'mochitest',
+ 'kwargs': {
+ 'flavor': 'mochitest',
+ 'subsuite': 'default',
+ 'chunk_by_dir': MOCHITEST_CHUNK_BY_DIR,
+ 'total_chunks': MOCHITEST_TOTAL_CHUNKS,
+ 'this_chunk': i,
+ 'test_paths': None,
+ },
+ }
+
+TEST_HELP = '''
+Test or tests to run. Tests can be specified by filename, directory, suite
+name or suite alias.
+
+The following test suites and aliases are supported: %s
+''' % ', '.join(sorted(TEST_SUITES))
+TEST_HELP = TEST_HELP.strip()
+
+
+@CommandProvider
+class Test(MachCommandBase):
+ @Command('test', category='testing', description='Run tests (detects the kind of test and runs it).')
+ @CommandArgument('what', default=None, nargs='*', help=TEST_HELP)
+ def test(self, what):
+ """Run tests from names or paths.
+
+ mach test accepts arguments specifying which tests to run. Each argument
+ can be:
+
+ * The path to a test file
+ * A directory containing tests
+ * A test suite name
+ * An alias to a test suite name (codes used on TreeHerder)
+
+ If no input is provided, tests will be run based on files changed in
+ the local tree. Relevant tests, tags, or flavors are determined by
+ IMPACTED_TESTS annotations in moz.build files relevant to the
+ changed files.
+
+ When paths or directories are given, they are first resolved to test
+ files known to the build system.
+
+ If resolved tests belong to more than one test type/flavor/harness,
+ the harness for each relevant type/flavor will be invoked. e.g. if
+ you specify a directory with xpcshell and browser chrome mochitests,
+ both harnesses will be invoked.
+ """
+ from mozbuild.testing import TestResolver
+
+ # Parse arguments and assemble a test "plan."
+ run_suites = set()
+ run_tests = []
+ resolver = self._spawn(TestResolver)
+
+ for entry in what:
+ # If the path matches the name or alias of an entire suite, run
+ # the entire suite.
+ if entry in TEST_SUITES:
+ run_suites.add(entry)
+ continue
+ suitefound = False
+ for suite, v in TEST_SUITES.items():
+ if entry in v.get('aliases', []):
+ run_suites.add(suite)
+ suitefound = True
+ if suitefound:
+ continue
+
+ # Now look for file/directory matches in the TestResolver.
+ relpath = self._wrap_path_argument(entry).relpath()
+ tests = list(resolver.resolve_tests(paths=[relpath]))
+ run_tests.extend(tests)
+
+ if not tests:
+ print('UNKNOWN TEST: %s' % entry, file=sys.stderr)
+
+ if not what:
+ # TODO: This isn't really related to try, and should be
+ # extracted to a common library for vcs interactions when it is
+ # introduced in bug 1185599.
+ from autotry import AutoTry
+ at = AutoTry(self.topsrcdir, resolver, self._mach_context)
+ changed_files = at.find_changed_files()
+ if changed_files:
+ print("Tests will be run based on modifications to the "
+ "following files:\n\t%s" % "\n\t".join(changed_files))
+
+ from mozbuild.frontend.reader import (
+ BuildReader,
+ EmptyConfig,
+ )
+ config = EmptyConfig(self.topsrcdir)
+ reader = BuildReader(config)
+ files_info = reader.files_info(changed_files)
+
+ paths, tags, flavors = set(), set(), set()
+ for info in files_info.values():
+ paths |= info.test_files
+ tags |= info.test_tags
+ flavors |= info.test_flavors
+
+ # This requires multiple calls to resolve_tests, because the test
+ # resolver returns tests that match every condition, while we want
+ # tests that match any condition. Bug 1210213 tracks implementing
+ # more flexible querying.
+ if tags:
+ run_tests = list(resolver.resolve_tests(tags=tags))
+ if paths:
+ run_tests += [t for t in resolver.resolve_tests(paths=paths)
+ if not (tags & set(t.get('tags', '').split()))]
+ if flavors:
+ run_tests = [t for t in run_tests if t['flavor'] not in flavors]
+ for flavor in flavors:
+ run_tests += list(resolver.resolve_tests(flavor=flavor))
+
+ if not run_suites and not run_tests:
+ print(UNKNOWN_TEST)
+ return 1
+
+ status = None
+ for suite_name in run_suites:
+ suite = TEST_SUITES[suite_name]
+
+ if 'mach_command' in suite:
+ res = self._mach_context.commands.dispatch(
+ suite['mach_command'], self._mach_context,
+ **suite['kwargs'])
+ if res:
+ status = res
+
+ buckets = {}
+ for test in run_tests:
+ key = (test['flavor'], test.get('subsuite', ''))
+ buckets.setdefault(key, []).append(test)
+
+ for (flavor, subsuite), tests in sorted(buckets.items()):
+ if flavor not in TEST_FLAVORS:
+ print(UNKNOWN_FLAVOR % flavor)
+ status = 1
+ continue
+
+ m = TEST_FLAVORS[flavor]
+ if 'mach_command' not in m:
+ print(UNKNOWN_FLAVOR % flavor)
+ status = 1
+ continue
+
+ kwargs = dict(m['kwargs'])
+ kwargs['subsuite'] = subsuite
+
+ res = self._mach_context.commands.dispatch(
+ m['mach_command'], self._mach_context,
+ test_objects=tests, **kwargs)
+ if res:
+ status = res
+
+ return status
+
+
+@CommandProvider
+class MachCommands(MachCommandBase):
+ @Command('cppunittest', category='testing',
+ description='Run cpp unit tests (C++ tests).')
+ @CommandArgument('test_files', nargs='*', metavar='N',
+ help='Test to run. Can be specified as one or more files or ' \
+ 'directories, or omitted. If omitted, the entire test suite is ' \
+ 'executed.')
+
+ def run_cppunit_test(self, **params):
+ import mozinfo
+ from mozlog import commandline
+ log = commandline.setup_logging("cppunittest",
+ {},
+ {"tbpl": sys.stdout})
+
+ # See if we have crash symbols
+ symbols_path = os.path.join(self.distdir, 'crashreporter-symbols')
+ if not os.path.isdir(symbols_path):
+ symbols_path = None
+
+ # If no tests specified, run all tests in main manifest
+ tests = params['test_files']
+ if len(tests) == 0:
+ tests = [os.path.join(self.distdir, 'cppunittests')]
+ manifest_path = os.path.join(self.topsrcdir, 'testing', 'cppunittest.ini')
+ else:
+ manifest_path = None
+
+ if conditions.is_android(self):
+ from mozrunner.devices.android_device import verify_android_device
+ verify_android_device(self, install=False)
+ return self.run_android_test(tests, symbols_path, manifest_path, log)
+
+ return self.run_desktop_test(tests, symbols_path, manifest_path, log)
+
+ def run_desktop_test(self, tests, symbols_path, manifest_path, log):
+ import runcppunittests as cppunittests
+ from mozlog import commandline
+
+ parser = cppunittests.CPPUnittestOptions()
+ commandline.add_logging_group(parser)
+ options, args = parser.parse_args()
+
+ options.symbols_path = symbols_path
+ options.manifest_path = manifest_path
+ options.xre_path = self.bindir
+
+ try:
+ result = cppunittests.run_test_harness(options, tests)
+ except Exception as e:
+ log.error("Caught exception running cpp unit tests: %s" % str(e))
+ result = False
+ raise
+
+ return 0 if result else 1
+
+ def run_android_test(self, tests, symbols_path, manifest_path, log):
+ import remotecppunittests as remotecppunittests
+ from mozlog import commandline
+
+ parser = remotecppunittests.RemoteCPPUnittestOptions()
+ commandline.add_logging_group(parser)
+ options, args = parser.parse_args()
+
+ options.symbols_path = symbols_path
+ options.manifest_path = manifest_path
+ options.xre_path = self.bindir
+ options.dm_trans = "adb"
+ options.local_lib = self.bindir.replace('bin', 'fennec')
+ for file in os.listdir(os.path.join(self.topobjdir, "dist")):
+ if file.endswith(".apk") and file.startswith("fennec"):
+ options.local_apk = os.path.join(self.topobjdir, "dist", file)
+ log.info("using APK: " + options.local_apk)
+ break
+
+ try:
+ result = remotecppunittests.run_test_harness(options, tests)
+ except Exception as e:
+ log.error("Caught exception running cpp unit tests: %s" % str(e))
+ result = False
+ raise
+
+ return 0 if result else 1
+
+def executable_name(name):
+ return name + '.exe' if sys.platform.startswith('win') else name
+
+@CommandProvider
+class CheckSpiderMonkeyCommand(MachCommandBase):
+ @Command('check-spidermonkey', category='testing', description='Run SpiderMonkey tests (JavaScript engine).')
+ @CommandArgument('--valgrind', action='store_true', help='Run jit-test suite with valgrind flag')
+
+ def run_checkspidermonkey(self, **params):
+ import subprocess
+ import sys
+
+ js = os.path.join(self.bindir, executable_name('js'))
+
+ print('Running jit-tests')
+ jittest_cmd = [os.path.join(self.topsrcdir, 'js', 'src', 'jit-test', 'jit_test.py'),
+ js, '--no-slow', '--jitflags=all']
+ if params['valgrind']:
+ jittest_cmd.append('--valgrind')
+
+ jittest_result = subprocess.call(jittest_cmd)
+
+ print('running jstests')
+ jstest_cmd = [os.path.join(self.topsrcdir, 'js', 'src', 'tests', 'jstests.py'),
+ js, '--jitflags=all']
+ jstest_result = subprocess.call(jstest_cmd)
+
+ print('running jsapi-tests')
+ jsapi_tests_cmd = [os.path.join(self.bindir, executable_name('jsapi-tests'))]
+ jsapi_tests_result = subprocess.call(jsapi_tests_cmd)
+
+ print('running check-style')
+ check_style_cmd = [sys.executable, os.path.join(self.topsrcdir, 'config', 'check_spidermonkey_style.py')]
+ check_style_result = subprocess.call(check_style_cmd, cwd=os.path.join(self.topsrcdir, 'js', 'src'))
+
+ print('running check-masm')
+ check_masm_cmd = [sys.executable, os.path.join(self.topsrcdir, 'config', 'check_macroassembler_style.py')]
+ check_masm_result = subprocess.call(check_masm_cmd, cwd=os.path.join(self.topsrcdir, 'js', 'src'))
+
+ print('running check-js-msg-encoding')
+ check_js_msg_cmd = [sys.executable, os.path.join(self.topsrcdir, 'config', 'check_js_msg_encoding.py')]
+ check_js_msg_result = subprocess.call(check_js_msg_cmd, cwd=self.topsrcdir)
+
+ all_passed = jittest_result and jstest_result and jsapi_tests_result and check_style_result and check_masm_result and check_js_msg_result
+
+ return all_passed
+
+@CommandProvider
+class JsapiTestsCommand(MachCommandBase):
+ @Command('jsapi-tests', category='testing', description='Run jsapi tests (JavaScript engine).')
+ @CommandArgument('test_name', nargs='?', metavar='N',
+ help='Test to run. Can be a prefix or omitted. If omitted, the entire ' \
+ 'test suite is executed.')
+
+ def run_jsapitests(self, **params):
+ import subprocess
+
+ bin_suffix = ''
+ if sys.platform.startswith('win'):
+ bin_suffix = '.exe'
+
+ print('running jsapi-tests')
+ jsapi_tests_cmd = [os.path.join(self.bindir, executable_name('jsapi-tests'))]
+ if params['test_name']:
+ jsapi_tests_cmd.append(params['test_name'])
+
+ jsapi_tests_result = subprocess.call(jsapi_tests_cmd)
+
+ return jsapi_tests_result
+
+def autotry_parser():
+ from autotry import arg_parser
+ return arg_parser()
+
+@CommandProvider
+class PushToTry(MachCommandBase):
+ def normalise_list(self, items, allow_subitems=False):
+ from autotry import parse_arg
+
+ rv = defaultdict(list)
+ for item in items:
+ parsed = parse_arg(item)
+ for key, values in parsed.iteritems():
+ rv[key].extend(values)
+
+ if not allow_subitems:
+ if not all(item == [] for item in rv.itervalues()):
+ raise ValueError("Unexpected subitems in argument")
+ return rv.keys()
+ else:
+ return rv
+
+ def validate_args(self, **kwargs):
+ from autotry import AutoTry
+ if not kwargs["paths"] and not kwargs["tests"] and not kwargs["tags"]:
+ print("Paths, tags, or tests must be specified as an argument to autotry.")
+ sys.exit(1)
+
+ if kwargs["platforms"] is None:
+ if 'AUTOTRY_PLATFORM_HINT' in os.environ:
+ kwargs["platforms"] = [os.environ['AUTOTRY_PLATFORM_HINT']]
+ else:
+ print("Platforms must be specified as an argument to autotry.")
+ sys.exit(1)
+
+ try:
+ platforms = self.normalise_list(kwargs["platforms"])
+ except ValueError as e:
+ print("Error parsing -p argument:\n%s" % e.message)
+ sys.exit(1)
+
+ try:
+ tests = (self.normalise_list(kwargs["tests"], allow_subitems=True)
+ if kwargs["tests"] else {})
+ except ValueError as e:
+ print("Error parsing -u argument (%s):\n%s" % (kwargs["tests"], e.message))
+ sys.exit(1)
+
+ try:
+ talos = (self.normalise_list(kwargs["talos"], allow_subitems=True)
+ if kwargs["talos"] else [])
+ except ValueError as e:
+ print("Error parsing -t argument:\n%s" % e.message)
+ sys.exit(1)
+
+ paths = []
+ for p in kwargs["paths"]:
+ p = mozpath.normpath(os.path.abspath(p))
+ if not (os.path.isdir(p) and p.startswith(self.topsrcdir)):
+ print('Specified path "%s" is not a directory under the srcdir,'
+ ' unable to specify tests outside of the srcdir' % p)
+ sys.exit(1)
+ if len(p) <= len(self.topsrcdir):
+ print('Specified path "%s" is at the top of the srcdir and would'
+ ' select all tests.' % p)
+ sys.exit(1)
+ paths.append(os.path.relpath(p, self.topsrcdir))
+
+ try:
+ tags = self.normalise_list(kwargs["tags"]) if kwargs["tags"] else []
+ except ValueError as e:
+ print("Error parsing --tags argument:\n%s" % e.message)
+ sys.exit(1)
+
+ extra_values = {k['dest'] for k in AutoTry.pass_through_arguments.values()}
+ extra_args = {k: v for k, v in kwargs.items()
+ if k in extra_values and v}
+
+ return kwargs["builds"], platforms, tests, talos, paths, tags, extra_args
+
+
+ @Command('try',
+ category='testing',
+ description='Push selected tests to the try server',
+ parser=autotry_parser)
+
+ def autotry(self, **kwargs):
+ """Autotry is in beta, please file bugs blocking 1149670.
+
+ Push the current tree to try, with the specified syntax.
+
+ Build options, platforms and regression tests may be selected
+ using the usual try options (-b, -p and -u respectively). In
+ addition, tests in a given directory may be automatically
+ selected by passing that directory as a positional argument to the
+ command. For example:
+
+ mach try -b d -p linux64 dom testing/web-platform/tests/dom
+
+ would schedule a try run for linux64 debug consisting of all
+ tests under dom/ and testing/web-platform/tests/dom.
+
+ Test selection using positional arguments is available for
+ mochitests, reftests, xpcshell tests and web-platform-tests.
+
+ Tests may be also filtered by passing --tag to the command,
+ which will run only tests marked as having the specified
+ tags e.g.
+
+ mach try -b d -p win64 --tag media
+
+ would run all tests tagged 'media' on Windows 64.
+
+ If both positional arguments or tags and -u are supplied, the
+ suites in -u will be run in full. Where tests are selected by
+ positional argument they will be run in a single chunk.
+
+ If no build option is selected, both debug and opt will be
+ scheduled. If no platform is selected a default is taken from
+ the AUTOTRY_PLATFORM_HINT environment variable, if set.
+
+ The command requires either its own mercurial extension ("push-to-try",
+ installable from mach mercurial-setup) or a git repo using git-cinnabar
+ (available at https://github.com/glandium/git-cinnabar).
+
+ """
+
+ from mozbuild.testing import TestResolver
+ from autotry import AutoTry
+
+ print("mach try is under development, please file bugs blocking 1149670.")
+
+ resolver_func = lambda: self._spawn(TestResolver)
+ at = AutoTry(self.topsrcdir, resolver_func, self._mach_context)
+
+ if kwargs["list"]:
+ at.list_presets()
+ sys.exit()
+
+ if kwargs["load"] is not None:
+ defaults = at.load_config(kwargs["load"])
+
+ if defaults is None:
+ print("No saved configuration called %s found in autotry.ini" % kwargs["load"],
+ file=sys.stderr)
+
+ for key, value in kwargs.iteritems():
+ if value in (None, []) and key in defaults:
+ kwargs[key] = defaults[key]
+
+ if kwargs["push"] and at.find_uncommited_changes():
+ print('ERROR please commit changes before continuing')
+ sys.exit(1)
+
+ if not any(kwargs[item] for item in ("paths", "tests", "tags")):
+ kwargs["paths"], kwargs["tags"] = at.find_paths_and_tags(kwargs["verbose"])
+
+ builds, platforms, tests, talos, paths, tags, extra = self.validate_args(**kwargs)
+
+ if paths or tags:
+ paths = [os.path.relpath(os.path.normpath(os.path.abspath(item)), self.topsrcdir)
+ for item in paths]
+ paths_by_flavor = at.paths_by_flavor(paths=paths, tags=tags)
+
+ if not paths_by_flavor and not tests:
+ print("No tests were found when attempting to resolve paths:\n\n\t%s" %
+ paths)
+ sys.exit(1)
+
+ if not kwargs["intersection"]:
+ paths_by_flavor = at.remove_duplicates(paths_by_flavor, tests)
+ else:
+ paths_by_flavor = {}
+
+ try:
+ msg = at.calc_try_syntax(platforms, tests, talos, builds, paths_by_flavor, tags,
+ extra, kwargs["intersection"])
+ except ValueError as e:
+ print(e.message)
+ sys.exit(1)
+
+ if kwargs["verbose"] and paths_by_flavor:
+ print('The following tests will be selected: ')
+ for flavor, paths in paths_by_flavor.iteritems():
+ print("%s: %s" % (flavor, ",".join(paths)))
+
+ if kwargs["verbose"] or not kwargs["push"]:
+ print('The following try syntax was calculated:\n%s' % msg)
+
+ if kwargs["push"]:
+ at.push_to_try(msg, kwargs["verbose"])
+
+ if kwargs["save"] is not None:
+ at.save_config(kwargs["save"], msg)
+
+
+def get_parser(argv=None):
+ parser = ArgumentParser()
+ parser.add_argument(dest="suite_name",
+ nargs=1,
+ choices=['mochitest'],
+ type=str,
+ help="The test for which chunk should be found. It corresponds "
+ "to the mach test invoked (only 'mochitest' currently).")
+
+ parser.add_argument(dest="test_path",
+ nargs=1,
+ type=str,
+ help="The test (any mochitest) for which chunk should be found.")
+
+ parser.add_argument('--total-chunks',
+ type=int,
+ dest='total_chunks',
+ required=True,
+ help='Total number of chunks to split tests into.',
+ default=None)
+
+ parser.add_argument('--chunk-by-runtime',
+ action='store_true',
+ dest='chunk_by_runtime',
+ help='Group tests such that each chunk has roughly the same runtime.',
+ default=False)
+
+ parser.add_argument('--chunk-by-dir',
+ type=int,
+ dest='chunk_by_dir',
+ help='Group tests together in the same chunk that are in the same top '
+ 'chunkByDir directories.',
+ default=None)
+
+ parser.add_argument('--disable-e10s',
+ action='store_false',
+ dest='e10s',
+ help='Find test on chunk with electrolysis preferences disabled.',
+ default=True)
+
+ parser.add_argument('-p', '--platform',
+ choices=['linux', 'linux64', 'mac', 'macosx64', 'win32', 'win64'],
+ dest='platform',
+ help="Platform for the chunk to find the test.",
+ default=None)
+
+ parser.add_argument('--debug',
+ action='store_true',
+ dest='debug',
+ help="Find the test on chunk in a debug build.",
+ default=False)
+
+ return parser
+
+
+def download_mozinfo(platform=None, debug_build=False):
+ temp_dir = tempfile.mkdtemp()
+ temp_path = os.path.join(temp_dir, "mozinfo.json")
+ args = [
+ 'mozdownload',
+ '-t', 'tinderbox',
+ '--ext', 'mozinfo.json',
+ '-d', temp_path,
+ ]
+ if platform:
+ if platform == 'macosx64':
+ platform = 'mac64'
+ args.extend(['-p', platform])
+ if debug_build:
+ args.extend(['--debug-build'])
+
+ subprocess.call(args)
+ return temp_dir, temp_path
+
+@CommandProvider
+class ChunkFinder(MachCommandBase):
+ @Command('find-test-chunk', category='testing',
+ description='Find which chunk a test belongs to (works for mochitest).',
+ parser=get_parser)
+ def chunk_finder(self, **kwargs):
+ total_chunks = kwargs['total_chunks']
+ test_path = kwargs['test_path'][0]
+ suite_name = kwargs['suite_name'][0]
+ _, dump_tests = tempfile.mkstemp()
+
+ from mozbuild.testing import TestResolver
+ resolver = self._spawn(TestResolver)
+ relpath = self._wrap_path_argument(test_path).relpath()
+ tests = list(resolver.resolve_tests(paths=[relpath]))
+ if len(tests) != 1:
+ print('No test found for test_path: %s' % test_path)
+ sys.exit(1)
+
+ flavor = tests[0]['flavor']
+ subsuite = tests[0]['subsuite']
+ args = {
+ 'totalChunks': total_chunks,
+ 'dump_tests': dump_tests,
+ 'chunkByDir': kwargs['chunk_by_dir'],
+ 'chunkByRuntime': kwargs['chunk_by_runtime'],
+ 'e10s': kwargs['e10s'],
+ 'subsuite': subsuite,
+ }
+
+ temp_dir = None
+ if kwargs['platform'] or kwargs['debug']:
+ self._activate_virtualenv()
+ self.virtualenv_manager.install_pip_package('mozdownload==1.17')
+ temp_dir, temp_path = download_mozinfo(kwargs['platform'], kwargs['debug'])
+ args['extra_mozinfo_json'] = temp_path
+
+ found = False
+ for this_chunk in range(1, total_chunks+1):
+ args['thisChunk'] = this_chunk
+ try:
+ self._mach_context.commands.dispatch(suite_name, self._mach_context, flavor=flavor, resolve_tests=False, **args)
+ except SystemExit:
+ pass
+ except KeyboardInterrupt:
+ break
+
+ fp = open(os.path.expanduser(args['dump_tests']), 'r')
+ tests = json.loads(fp.read())['active_tests']
+ for test in tests:
+ if test_path == test['path']:
+ if 'disabled' in test:
+ print('The test %s for flavor %s is disabled on the given platform' % (test_path, flavor))
+ else:
+ print('The test %s for flavor %s is present in chunk number: %d' % (test_path, flavor, this_chunk))
+ found = True
+ break
+
+ if found:
+ break
+
+ if not found:
+ raise Exception("Test %s not found." % test_path)
+ # Clean up the file
+ os.remove(dump_tests)
+ if temp_dir:
+ shutil.rmtree(temp_dir)