Bug 1437912 - [moztest] Map flavors and subsuites to a suite definition, r?gbrown draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Tue, 13 Feb 2018 14:16:46 -0500
changeset 756176 832a5d35c0735e108d457c61359e2be4754b4959
parent 756175 e0361e8d3b1174559f9220e5c061339b48c1b26a
child 756177 c8130146ca908185ebbc180f38303c5986d6df65
push id99404
push userahalberstadt@mozilla.com
push dateFri, 16 Feb 2018 15:03:51 +0000
reviewersgbrown
bugs1437912
milestone60.0a1
Bug 1437912 - [moztest] Map flavors and subsuites to a suite definition, r?gbrown The end goal here is to be able to use |mach try fuzzy <path>| with tests that belong to a subsuite. To do this, we need a unique 'task_regex' value for each subsuite so that we can map a test path back to a set of tasks. This removes the TEST_FLAVORS dict (which was mostly just a redefinition of the data in TEST_SUITES), and instead provides two new private mappings: <flavor> -> suite definition (<flavor>, <subsuite>) -> suite definition To retrieve a suite definition given a flavor/subsuite, consumers can now call get_suite_definition. MozReview-Commit-ID: 2pe1v1IHUVy
testing/mach_commands.py
testing/mozbase/moztest/moztest/resolve.py
tools/tryselect/selectors/fuzzy.py
--- a/testing/mach_commands.py
+++ b/testing/mach_commands.py
@@ -34,17 +34,17 @@ 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
+I know you are trying to run a %s%s test. Unfortunately, I can't run those
 tests yet. Sorry!
 '''.strip()
 
 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
@@ -106,17 +106,17 @@ class Test(MachCommandBase):
         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 mozlog.commandline import setup_logging
-        from moztest.resolve import TestResolver, TEST_FLAVORS, TEST_SUITES
+        from moztest.resolve import get_suite_definition, TestResolver, TEST_SUITES
 
         resolver = self._spawn(TestResolver)
         run_suites, run_tests = resolver.resolve_metadata(what)
 
         if not run_suites and not run_tests:
             print(UNKNOWN_TEST)
             return 1
 
@@ -141,30 +141,25 @@ class Test(MachCommandBase):
                     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]
+            m = get_suite_definition(flavor, subsuite)
             if 'mach_command' not in m:
-                print(UNKNOWN_FLAVOR % flavor)
+                substr = '-{}'.format(subsuite) if subsuite else ''
+                print(UNKNOWN_FLAVOR % (flavor, substr))
                 status = 1
                 continue
 
             kwargs = dict(m['kwargs'])
             kwargs['log'] = log
-            kwargs['subsuite'] = subsuite
 
             res = self._mach_context.commands.dispatch(
                 m['mach_command'], self._mach_context,
                 argv=extra_args, test_objects=tests, **kwargs)
             if res:
                 status = res
 
         log.shutdown()
--- a/testing/mozbase/moztest/moztest/resolve.py
+++ b/testing/mozbase/moztest/moztest/resolve.py
@@ -45,124 +45,80 @@ TEST_SUITES = {
     },
     'check-spidermonkey': {
         'aliases': ('sm',),
         'mach_command': 'check-spidermonkey',
         'kwargs': {'valgrind': False},
     },
     # TODO(ato): integrate geckodriver tests with moz.build
     'geckodriver': {
+        'aliases': ('testing/geckodriver',),
         'mach_command': 'geckodriver-test',
-        'aliases': ('testing/geckodriver',),
         'kwargs': {},
     },
+    'marionette': {
+        'aliases': ('mn',),
+        'mach_command': 'marionette',
+        'kwargs': {'tests': None},
+    },
     'mochitest-a11y': {
         'aliases': ('a11y', 'ally'),
         'mach_command': 'mochitest',
         'kwargs': {'flavor': 'a11y', 'test_paths': None},
+        'task_regex': 'mochitest-a11y(?:-1)?$',
     },
     'mochitest-browser': {
         'aliases': ('bc', 'browser-chrome'),
         'mach_command': 'mochitest',
         'kwargs': {'flavor': 'browser-chrome', 'test_paths': None},
+        'task_regex': 'mochitest-browser-chrome(?:-e10s)?(?:-1)?$',
     },
     'mochitest-chrome': {
         'aliases': ('mc',),
         'mach_command': 'mochitest',
         'kwargs': {'flavor': 'chrome', 'test_paths': None},
+        'task_regex': 'mochitest-chrome(?:-e10s)?(?:-1)?$',
     },
     'mochitest-devtools': {
         'aliases': ('dt', 'devtools-chrome'),
         'mach_command': 'mochitest',
         'kwargs': {'subsuite': 'devtools', 'test_paths': None},
+        'task_regex': 'mochitest-devtools-chrome(?:-e10s)?(?:-1)?$',
     },
     'mochitest-plain': {
         'aliases': ('mp', 'plain',),
         'mach_command': 'mochitest',
         'kwargs': {'flavor': 'plain', 'test_paths': None},
+        'task_regex': 'mochitest(?:-e10s)?(?:-1)?$',
     },
     'python': {
         'mach_command': 'python-test',
         'kwargs': {'tests': None},
     },
     'reftest': {
         'aliases': ('rr',),
         'mach_command': 'reftest',
         'kwargs': {'tests': None},
+        'task_regex': '(opt|debug)-reftest(?:-no-accel|-gpu|-stylo)?(?:-e10s)?(?:-1)?$',
     },
     'web-platform-tests': {
         'aliases': ('wpt',),
         'mach_command': 'web-platform-tests',
-        'kwargs': {}
+        'kwargs': {'include': []},
+        'task_regex': 'web-platform-tests(?:-reftests|-wdspec)?(?:-e10s)?(?:-1)?$',
     },
     'valgrind': {
         'aliases': ('v',),
         'mach_command': 'valgrind-test',
         'kwargs': {},
     },
     'xpcshell': {
         'aliases': ('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': []},
-        'task_regex': 'mochitest-a11y(?:-1)?$',
-    },
-    'browser-chrome': {
-        'mach_command': 'mochitest',
-        'kwargs': {'flavor': 'browser-chrome', 'test_paths': []},
-        'task_regex': 'mochitest-browser-chrome(?:-e10s)?(?:-1)?$',
-    },
-    'crashtest': {},
-    'chrome': {
-        'mach_command': 'mochitest',
-        'kwargs': {'flavor': 'chrome', 'test_paths': []},
-        'task_regex': 'mochitest-chrome(?:-e10s)?(?:-1)?$',
-    },
-    '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': []},
-        'task_regex': 'mochitest(?:-e10s)?(?:-1)?$',
-    },
-    'python': {
-        'mach_command': 'python-test',
-        'kwargs': {},
-    },
-    'reftest': {
-        'mach_command': 'reftest',
-        'kwargs': {'tests': []},
-        'task_regex': '(opt|debug)-reftest(?:-no-accel|-gpu|-stylo)?(?:-e10s)?(?:-1)?$',
-    },
-    'steeplechase': {},
-    'web-platform-tests': {
-        'mach_command': 'web-platform-tests',
-        'kwargs': {'include': []},
-        'task_regex': 'web-platform-tests(?:-reftests|-wdspec)?(?:-e10s)?(?:-1)?$',
-    },
-    'xpcshell': {
-        'mach_command': 'xpcshell-test',
-        'kwargs': {'test_paths': []},
         'task_regex': 'xpcshell(?:-1)?$',
     },
 }
 
 for i in range(1, MOCHITEST_TOTAL_CHUNKS + 1):
     TEST_SUITES['mochitest-%d' % i] = {
         'aliases': ('m%d' % i,),
         'mach_command': 'mochitest',
@@ -171,16 +127,64 @@ for i in range(1, MOCHITEST_TOTAL_CHUNKS
             'subsuite': 'default',
             'chunk_by_dir': MOCHITEST_CHUNK_BY_DIR,
             'total_chunks': MOCHITEST_TOTAL_CHUNKS,
             'this_chunk': i,
             'test_paths': None,
         },
     }
 
+_test_flavors = {
+    'a11y': 'mochitest-a11y',
+    'browser-chrome': 'mochitest-browser',
+    'chrome': 'mochitest-chrome',
+    'crashtest': '',
+    'firefox-ui-functional': 'firefox-ui-functional',
+    'firefox-ui-update': 'firefox-ui-update',
+    'marionette': 'marionette',
+    'mochitest': 'mochitest-plain',
+    'python': 'python',
+    'reftest': 'reftest',
+    'steeplechase': '',
+    'web-platform-tests': 'web-platform-tests',
+    'xpcshell': 'xpcshell',
+}
+
+_test_subsuites = {
+    ('browser-chrome', 'devtools'): 'mochitest-devtools',
+}
+
+
+def get_suite_definition(flavor, subsuite=None, strict=False):
+    """Return a suite definition given a flavor and optional subsuite.
+
+    If strict is True, a subsuite must have its own entry in TEST_SUITES.
+    Otherwise, the entry for 'flavor' will be returned with the 'subsuite'
+    keyword arg set.
+
+    With or without strict mode, an empty dict will be returned if no
+    matching suite definition was found.
+    """
+    if not subsuite:
+        suite_name = _test_flavors.get(flavor)
+        return TEST_SUITES.get(suite_name, {}).copy()
+
+    suite_name = _test_subsuites.get((flavor, subsuite))
+    if suite_name or strict:
+        return TEST_SUITES.get(suite_name, {}).copy()
+
+    suite_name = _test_flavors.get(flavor)
+    if suite_name not in TEST_SUITES:
+        return {}
+
+    suite = TEST_SUITES[suite_name].copy()
+    suite.setdefault('kwargs', {})
+    suite['kwargs']['subsuite'] = subsuite
+    return suite
+
 
 def rewrite_test_base(test, new_base, honor_install_to_subdir=False):
     """Rewrite paths in a test to be under a new base path.
 
     This is useful for running tests from a separate location from where they
     were defined.
 
     honor_install_to_subdir and the underlying install-to-subdir field are a
--- a/tools/tryselect/selectors/fuzzy.py
+++ b/tools/tryselect/selectors/fuzzy.py
@@ -8,17 +8,17 @@ import os
 import platform
 import re
 import subprocess
 import sys
 from distutils.spawn import find_executable
 
 from mozboot.util import get_state_dir
 from mozterm import Terminal
-from moztest.resolve import TestResolver, TEST_FLAVORS
+from moztest.resolve import TestResolver, get_suite_definition
 
 from .. import preset as pset
 from ..cli import BaseTryParser
 from ..tasks import generate_tasks
 from ..vcs import VCSHelper
 
 terminal = Terminal()
 
@@ -171,19 +171,27 @@ def format_header():
         shortcuts.append('{t.white}{action}{t.normal}: {t.yellow}<{key}>{t.normal}'.format(
                          t=terminal, action=action, key=key))
     return FZF_HEADER.format(shortcuts=', '.join(shortcuts), t=terminal)
 
 
 def filter_by_paths(tasks, paths):
     resolver = TestResolver.from_environment(cwd=here)
     run_suites, run_tests = resolver.resolve_metadata(paths)
-    flavors = set([t['flavor'] for t in run_tests])
-    task_regexes = [TEST_FLAVORS[f]['task_regex']
-                    for f in flavors if 'task_regex' in TEST_FLAVORS[f]]
+    flavors = set([(t['flavor'], t.get('subsuite')) for t in run_tests])
+
+    task_regexes = set()
+    for flavor, subsuite in flavors:
+        suite = get_suite_definition(flavor, subsuite, strict=True)
+        if 'task_regex' not in suite:
+            print("warning: no tasks could be resolved from flavor '{}'{}".format(
+                    flavor, " and subsuite '{}'".format(subsuite) if subsuite else ""))
+            continue
+
+        task_regexes.add(suite['task_regex'])
 
     def match_task(task):
         return any(re.search(pattern, task) for pattern in task_regexes)
 
     return filter(match_task, tasks)
 
 
 def run_fuzzy_try(update=False, query=None, templates=None, full=False, parameters=None,
@@ -191,25 +199,27 @@ def run_fuzzy_try(update=False, query=No
                   paths=None, **kwargs):
     if mod_presets:
         return getattr(pset, mod_presets)(section='fuzzy')
 
     fzf = fzf_bootstrap(update)
 
     if not fzf:
         print(FZF_NOT_FOUND)
-        return
+        return 1
 
     vcs = VCSHelper.create()
     vcs.check_working_directory(push)
 
     all_tasks = generate_tasks(parameters, full, root=vcs.root)
 
     if paths:
         all_tasks = filter_by_paths(all_tasks, paths)
+        if not all_tasks:
+            return 1
 
     key_shortcuts = [k + ':' + v for k, v in fzf_shortcuts.iteritems()]
     cmd = [
         fzf, '-m',
         '--bind', ','.join(key_shortcuts),
         '--header', format_header(),
         # Using python to split the preview string is a bit convoluted,
         # but is guaranteed to be available on all platforms.