Recursively find all tests in a git repo.

This method is necessary to allow good code organization (i.e., unittests live
adjacent to the source files they test) in the new infra.git repository.

R=maruel@chromium.org

Review URL: https://codereview.chromium.org/281013002

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@270564 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/presubmit_canned_checks.py b/presubmit_canned_checks.py
index bd3c361..56031b2 100644
--- a/presubmit_canned_checks.py
+++ b/presubmit_canned_checks.py
@@ -562,6 +562,36 @@
   return results
 
 
+def GetUnitTestsRecursively(input_api, output_api, directory,
+                            whitelist, blacklist):
+  """Gets all files in the directory tree (git repo) that match the whitelist.
+
+  Restricts itself to only find files within the Change's source repo, not
+  dependencies.
+  """
+  def check(filename):
+    return (any(input_api.re.match(f, filename) for f in whitelist) and
+            not any(input_api.re.match(f, filename) for f in blacklist))
+
+  tests = []
+
+  to_run = found = 0
+  for filepath in input_api.change.AllFiles(directory):
+    found += 1
+    if check(filepath):
+      to_run += 1
+      tests.append(filepath)
+  input_api.logging.debug('Found %d files, running %d' % (found, to_run))
+  if not to_run:
+    return [
+        output_api.PresubmitPromptWarning(
+          'Out of %d files, found none that matched w=%r, b=%r in directory %s'
+          % (found, whitelist, blacklist, directory))
+    ]
+
+  return GetUnitTests(input_api, output_api, tests)
+
+
 def GetPythonUnitTests(input_api, output_api, unit_tests):
   """Run the unit tests out of process, capture the output and use the result
   code to determine success.
@@ -744,7 +774,7 @@
   if True:
     return [GetPylintCmd(files)]
   else:
-    return map(GetPylintCmd, files)
+    return map(lambda x: GetPylintCmd([x]), files)
 
 
 def RunPylint(input_api, *args, **kwargs):
diff --git a/presubmit_support.py b/presubmit_support.py
index 111db58..e929e31 100755
--- a/presubmit_support.py
+++ b/presubmit_support.py
@@ -869,6 +869,10 @@
       raise AttributeError(self, attr)
     return self.tags.get(attr)
 
+  def AllFiles(self, root=None):
+    """List all files under source control in the repo."""
+    raise NotImplementedError()
+
   def AffectedFiles(self, include_dirs=False, include_deletes=True,
                     file_filter=None):
     """Returns a list of AffectedFile instances for all files in the change.
@@ -965,11 +969,23 @@
     return [os.path.join(self.RepositoryRoot(), f[1])
             for f in changelists[self.Name()]]
 
+  def AllFiles(self, root=None):
+    """List all files under source control in the repo."""
+    root = root or self.RepositoryRoot()
+    return subprocess.check_output(
+        ['svn', 'ls', '-R', '.'], cwd=root).splitlines()
+
 
 class GitChange(Change):
   _AFFECTED_FILES = GitAffectedFile
   scm = 'git'
 
+  def AllFiles(self, root=None):
+    """List all files under source control in the repo."""
+    root = root or self.RepositoryRoot()
+    return subprocess.check_output(
+        ['git', 'ls-files', '--', '.'], cwd=root).splitlines()
+
 
 def ListRelevantPresubmitFiles(files, root):
   """Finds all presubmit files that apply to a given set of source files.
diff --git a/tests/presubmit_unittest.py b/tests/presubmit_unittest.py
index 2f973bd..23304f0 100755
--- a/tests/presubmit_unittest.py
+++ b/tests/presubmit_unittest.py
@@ -1773,7 +1773,7 @@
 class ChangeUnittest(PresubmitTestsBase):
   def testMembersChanged(self):
     members = [
-        'AbsoluteLocalPaths', 'AffectedFiles', 'AffectedTextFiles',
+        'AbsoluteLocalPaths', 'AffectedFiles', 'AffectedTextFiles', 'AllFiles',
         'DescriptionText', 'FullDescriptionText', 'LocalPaths', 'Name',
         'RepositoryRoot', 'RightHandSideLines', 'ServerPaths',
         'SetDescriptionText', 'TAG_LINE_RE',
@@ -1885,7 +1885,7 @@
       'RunPythonUnitTests', 'RunPylint',
       'RunUnitTests', 'RunUnitTestsInDirectory',
       'GetPythonUnitTests', 'GetPylint',
-      'GetUnitTests', 'GetUnitTestsInDirectory',
+      'GetUnitTests', 'GetUnitTestsInDirectory', 'GetUnitTestsRecursively',
     ]
     # If this test fails, you should add the relevant test.
     self.compareMembers(presubmit_canned_checks, members)