Bug 1414399 - [moztest] Refactor |mach test|'s resolving logic into moztest.resolve r=gps
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Wed, 08 Nov 2017 09:59:51 -0500
changeset 444057 3170c6d82e89453a2301a8a8f594df2254db014e
parent 444056 1ede5a47bb9877a7bd01eb4303d65b8462f1d32c
child 444058 6646fb7959a8c7c78179fa09b6522b7a0f805f97
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1414399
milestone58.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 1414399 - [moztest] Refactor |mach test|'s resolving logic into moztest.resolve r=gps 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)