Bug 1414399 - [moztest] Refactor |mach test|'s resolving logic into moztest.resolve draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Wed, 08 Nov 2017 09:59:51 -0500
changeset 694981 a11da56ff23a9d3d47c457f90984c8f01e1e367d
parent 694980 ca82d9ffa2657574b35ff769765e178b376431c4
child 739479 e1d9628e16905383f69f3b7cb604d26cd606e5ed
push id88294
push userahalberstadt@mozilla.com
push dateWed, 08 Nov 2017 15:02:52 +0000
bugs1414399
milestone58.0a1
Bug 1414399 - [moztest] Refactor |mach test|'s resolving logic into moztest.resolve The code in |mach test| for test resolving, should get merged with the TestResolver class in moztest.resolve. This way it can be shared with other modules and we'll have a single canonical place for all our test resolving logic. MozReview-Commit-ID: IHRXXi5mB4G
testing/mach_commands.py
testing/mozbase/moztest/moztest/resolve.py
testing/mozbase/moztest/tests/test_resolve.py
tools/tryselect/mach_commands.py
tools/tryselect/selectors/syntax.py
--- a/testing/mach_commands.py
+++ b/testing/mach_commands.py
@@ -15,16 +15,17 @@ import shutil
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
 from mozbuild.base import MachCommandBase, MachCommandConditions as conditions
+from moztest.resolve import TEST_SUITES
 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
@@ -36,157 +37,16 @@ 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': {},
-    },
-    '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()
 
@@ -219,71 +79,19 @@ class Test(MachCommandBase):
         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 moztest.resolve import TestResolver
-
-        # Parse arguments and assemble a test "plan."
-        run_suites = set()
-        run_tests = []
+        from moztest.resolve import TestResolver, TEST_FLAVORS, TEST_SUITES
         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:
-            from tryselect.selectors.syntax import AutoTry
-            at = AutoTry(self.topsrcdir, resolver, self._mach_context)
-            res = at.find_paths_and_metadata(False, detect_paths=True)
-            paths = res['paths']
-            tags = res['tags']
-            flavors = res['flavors']
-
-            if paths:
-                print("Tests will be run based on modifications to the "
-                      "following files:\n\t%s" % "\n\t".join(paths))
-
-            # 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))
+        run_suites, run_tests = resolver.resolve_metadata(what)
 
         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]
--- a/testing/mozbase/moztest/moztest/resolve.py
+++ b/testing/mozbase/moztest/moztest/resolve.py
@@ -1,22 +1,169 @@
 # 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, unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
 
 import cPickle as pickle
 import os
+import sys
 from collections import defaultdict
 
 import manifestparser
 import mozpack.path as mozpath
 from mozbuild.base import MozbuildObject
 from mozbuild.util import OrderedDefaultDict
+from mozbuild.frontend.reader import BuildReader, EmptyConfig
+from mozversioncontrol import get_repository_object
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+
+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': {},
+    },
+    '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,
+        },
+    }
 
 
 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.
 
@@ -76,17 +223,16 @@ class TestMetadata(object):
                 flavor = metadata.get('flavor')
                 self._tests_by_flavor[flavor].add(path)
 
     def tests_with_flavor(self, flavor):
         """Obtain all tests having the specified flavor.
 
         This is a generator of dicts describing each test.
         """
-
         for path in sorted(self._tests_by_flavor.get(flavor, [])):
             yield self._tests_by_path[path]
 
     def resolve_tests(self, paths=None, flavor=None, subsuite=None, under_path=None,
                       tags=None):
         """Resolve tests from an identifier.
 
         This is a generator of dicts describing each test.
@@ -204,16 +350,24 @@ class TestResolver(MozbuildObject):
             'chrome': os.path.join(self.topobjdir, '_tests', 'testing',
                                    'mochitest', 'chrome'),
             'mochitest': os.path.join(self.topobjdir, '_tests', 'testing',
                                       'mochitest', 'tests'),
             'web-platform-tests': os.path.join(self.topobjdir, '_tests', 'testing',
                                                'web-platform'),
             'xpcshell': os.path.join(self.topobjdir, '_tests', 'xpcshell'),
         }
+        self._vcs = None
+        self.verbose = False
+
+    @property
+    def vcs(self):
+        if not self._vcs:
+            self._vcs = get_repository_object(self.topsrcdir)
+        return self._vcs
 
     def resolve_tests(self, cwd=None, **kwargs):
         """Resolve tests in the context of the current environment.
 
         This is a more intelligent version of TestMetadata.resolve_tests().
 
         This function provides additional massaging and filtering of low-level
         results.
@@ -247,8 +401,78 @@ class TestResolver(MozbuildObject):
         for test in result:
             rewrite_base = self._test_rewrites.get(test['flavor'], None)
 
             if rewrite_base:
                 yield rewrite_test_base(test, rewrite_base,
                                         honor_install_to_subdir=True)
             else:
                 yield test
+
+    def get_outgoing_metadata(self):
+        paths, tags, flavors = set(), set(), set()
+        changed_files = self.vcs.get_outgoing_files('AM')
+        if changed_files:
+            config = EmptyConfig(self.topsrcdir)
+            reader = BuildReader(config)
+            files_info = reader.files_info(changed_files)
+
+            for path, info in files_info.items():
+                paths |= info.test_files
+                tags |= info.test_tags
+                flavors |= info.test_flavors
+
+        return {
+            'paths': paths,
+            'tags': tags,
+            'flavors': flavors,
+        }
+
+    def resolve_metadata(self, what):
+        """Resolve tests based on the given metadata. If not specified, metadata
+        from outgoing files will be used instead.
+        """
+        # Parse arguments and assemble a test "plan."
+        run_suites = set()
+        run_tests = []
+
+        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(self.resolve_tests(paths=[relpath]))
+            run_tests.extend(tests)
+
+            if not tests:
+                print('UNKNOWN TEST: %s' % entry, file=sys.stderr)
+
+        if not what:
+            res = self.get_outgoing_metadata()
+            paths, tags, flavors = (res[key] for key in ('paths', 'tags', '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(self.resolve_tests(tags=tags))
+            if paths:
+                run_tests += [t for t in self.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(self.resolve_tests(flavor=flavor))
+
+        return run_suites, run_tests
--- a/testing/mozbase/moztest/tests/test_resolve.py
+++ b/testing/mozbase/moztest/tests/test_resolve.py
@@ -8,17 +8,16 @@ from __future__ import absolute_import, 
 import cPickle as pickle
 import os
 import shutil
 import tempfile
 import unittest
 
 import mozpack.path as mozpath
 import mozunit
-import pytest
 from mozbuild.base import MozbuildObject
 from mozfile import NamedTemporaryFile
 
 from moztest.resolve import (
     TestMetadata,
     TestResolver,
 )
 
@@ -319,11 +318,27 @@ class TestTestResolver(Base):
             self.assertTrue(t['file_relpath'].startswith('mobile'))
 
         tests = list(r.resolve_tests(paths=['**/**.js', 'accessible/**']))
         self.assertEqual(len(tests), 7)
         for t in tests:
             path = t['file_relpath']
             self.assertTrue(path.startswith('accessible') or path.endswith('.js'))
 
+    def test_resolve_metadata(self):
+        """Test finding metadata from outgoing files."""
+        r = self._get_resolver()
+
+        suites, tests = r.resolve_metadata(['bc'])
+        assert suites == {'mochitest-browser'}
+        assert tests == []
+
+        suites, tests = r.resolve_metadata(['mochitest-a11y', 'browser', 'xpcshell'])
+        assert suites == {'mochitest-a11y', 'xpcshell'}
+        assert sorted(t['file_relpath'] for t in tests) == [
+            'devtools/client/markupview/test/browser_markupview_copy_image_data.js',
+            'image/test/browser/browser_bug666317.js',
+            'mobile/android/tests/browser/junit3/src/TestDistribution.java',
+        ]
+
 
 if __name__ == '__main__':
     mozunit.main()
--- a/tools/tryselect/mach_commands.py
+++ b/tools/tryselect/mach_commands.py
@@ -200,30 +200,26 @@ class TrySelect(MachCommandBase):
         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 moztest.resolve import TestResolver
         from tryselect.selectors.syntax import AutoTry
 
         try:
             if self.substs.get("MOZ_ARTIFACT_BUILDS"):
                 kwargs['local_artifact_build'] = True
         except BuildEnvironmentNotFoundException:
             # If we don't have a build locally, we can't tell whether
             # an artifact build is desired, but we still want the
             # command to succeed, if possible.
             pass
 
         config_status = os.path.join(self.topobjdir, 'config.status')
         if (kwargs['paths'] or kwargs['tags']) and not config_status:
             print(CONFIG_ENVIRONMENT_NOT_FOUND)
             sys.exit(1)
 
-        def resolver_func():
-            return self._spawn(TestResolver)
-
-        at = AutoTry(self.topsrcdir, resolver_func, self._mach_context)
+        at = AutoTry(self.topsrcdir, self._mach_context)
         return at.run(**kwargs)
--- a/tools/tryselect/selectors/syntax.py
+++ b/tools/tryselect/selectors/syntax.py
@@ -5,20 +5,24 @@
 from __future__ import absolute_import, print_function, unicode_literals
 
 import os
 import re
 import sys
 from collections import defaultdict
 
 import mozpack.path as mozpath
+from moztest.resolve import TestResolver
+
 from .. import preset
 from ..cli import BaseTryParser
 from ..vcs import VCSHelper
 
+here = os.path.abspath(os.path.dirname(__file__))
+
 
 class SyntaxParser(BaseTryParser):
     name = 'syntax'
     arguments = [
         [['paths'],
          {'nargs': '*',
           'help': 'Paths to search for tests to run on try.',
           }],
@@ -297,27 +301,26 @@ class AutoTry(object):
         "marionette",
         "marionette-e10s",
         "mochitests",
         "reftest",
         "web-platform-tests",
         "xpcshell",
     ]
 
-    def __init__(self, topsrcdir, resolver_func, mach_context):
+    def __init__(self, topsrcdir, mach_context):
         self.topsrcdir = topsrcdir
-        self._resolver_func = resolver_func
         self._resolver = None
         self.mach_context = mach_context
         self.vcs = VCSHelper.create()
 
     @property
     def resolver(self):
         if self._resolver is None:
-            self._resolver = self._resolver_func()
+            self._resolver = TestResolver.from_environment(cwd=here)
         return self._resolver
 
     def split_try_string(self, data):
         return re.findall(r'(?:\[.*?\]|\S)+', data)
 
     def paths_by_flavor(self, paths=None, tags=None):
         paths_by_flavor = defaultdict(set)
 
@@ -454,52 +457,16 @@ class AutoTry(object):
                 for e in value:
                     parts.append(arg)
                     parts.append(e)
             if action in ('store_true', 'store_false'):
                 parts.append(arg)
 
         return " ".join(parts)
 
-    def find_paths_and_metadata(self, verbose, detect_paths):
-        paths, tags, flavors = set(), set(), set()
-        changed_files = self.vcs.files_changed
-        if changed_files and detect_paths:
-            if verbose:
-                print("Pushing tests 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)
-
-            for path, info in files_info.items():
-                paths |= info.test_files
-                tags |= info.test_tags
-                flavors |= info.test_flavors
-
-            if verbose:
-                if paths:
-                    print("Pushing tests based on the following patterns:\n\t%s" %
-                          "\n\t".join(paths))
-                if tags:
-                    print("Pushing tests based on the following tags:\n\t%s" %
-                          "\n\t".join(tags))
-
-        return {
-            'paths': paths,
-            'tags': tags,
-            'flavors': flavors,
-        }
-
     def normalise_list(self, items, allow_subitems=False):
         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:
@@ -586,20 +553,23 @@ class AutoTry(object):
                 print("No saved configuration called %s found in autotry.ini" % kwargs["preset"],
                       file=sys.stderr)
 
             for key, value in kwargs.iteritems():
                 if value in (None, []) and key in defaults:
                     kwargs[key] = defaults[key]
 
         if not any(kwargs[item] for item in ("paths", "tests", "tags")):
-            res = self.find_paths_and_metadata(kwargs['verbose'],
-                                               kwargs['detect_paths'])
-            kwargs['paths'] = res['paths']
-            kwargs['tags'] = res['tags']
+            if kwargs['detect_paths']:
+                res = self.resolver.get_outgoing_metadata()
+                kwargs['paths'] = res['paths']
+                kwargs['tags'] = res['tags']
+            else:
+                kwargs['paths'] = set()
+                kwargs['tags'] = set()
 
         builds, platforms, tests, talos, jobs, 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 = self.paths_by_flavor(paths=paths, tags=tags)