Bug 1209188 - Add a mode to mach test to run impacted tests according to moz.build dependency info. r=ahal
authorChris Manchester <cmanchester@mozilla.com>
Thu, 01 Oct 2015 10:10:45 -0700
changeset 300785 76af185df32b370568f209c4d375abcac96c1a79
parent 300784 14eb7f6689b3a4205197d4f72a7d68ecc5be5914
child 300786 65257cf4c5f8f534cc03cc5e090515a35b314183
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersahal
bugs1209188
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1209188 - Add a mode to mach test to run impacted tests according to moz.build dependency info. r=ahal This modifies the behavior of running |./mach test| with no arguments to run tests relevant to local file changes, as specified by IMPACTED_TESTS annotations in moz.build files relevant to the changed files.
testing/mach_commands.py
--- a/testing/mach_commands.py
+++ b/testing/mach_commands.py
@@ -17,22 +17,23 @@ from mach.decorators import (
     CommandProvider,
     Command,
 )
 
 from mozbuild.base import MachCommandBase
 from mozbuild.base import MachCommandConditions as conditions
 from argparse import ArgumentParser
 
+UNKNOWN_TEST = '''
+I was unable to find tests from the given argument(s).
 
-UNKNOWN_TEST = '''
-I was unable to find tests in the argument(s) given.
-
-You need to specify a test directory, filename, test suite name, or
-abbreviation.
+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 = '''
@@ -194,16 +195,21 @@ class Test(MachCommandBase):
         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.
         """
@@ -231,37 +237,70 @@ class Test(MachCommandBase):
             # 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
 
-            elif 'make_target' in suite:
-                res = self._run_make(target=suite['make_target'],
-                    pass_thru=True)
-                if res:
-                    status = res
-
         buckets = {}
         for test in run_tests:
             key = (test['flavor'], test['subsuite'])
             buckets.setdefault(key, []).append(test)
 
         for (flavor, subsuite), tests in sorted(buckets.items()):
             if flavor not in TEST_FLAVORS:
                 print(UNKNOWN_FLAVOR % flavor)