[autotest] Add suite_enumerator.py

suite_enumerator.py is a command line tool that enumerates all tests
in a given dynamic_suite-based test suite.

I also did some refactoring so that code which imports
dynamic_suite.py will be able to easily enumerate all tests in a suite
as well.

BUG=chromium-os:26667
TEST=unit tests
TEST=./test_suites/suite_enumerator.py -a /path/to/autotest/files bvt
STATUS=Fixed

Change-Id: I16cc89eae0d04b6b19b8715ae9e3787bdd4f656d
Reviewed-on: https://gerrit.chromium.org/gerrit/16390
Tested-by: Chris Masone <cmasone@chromium.org>
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Ready: Chris Masone <cmasone@chromium.org>
diff --git a/server/cros/dynamic_suite.py b/server/cros/dynamic_suite.py
index d184e68..ed023fa 100644
--- a/server/cros/dynamic_suite.py
+++ b/server/cros/dynamic_suite.py
@@ -217,7 +217,7 @@
 
 
     @staticmethod
-    def create_cf_getter(build):
+    def create_ds_getter(build):
         """
         @param build: the build on which we're running this suite.
         @return a FileSystemGetter instance that looks under |autotest_dir|.
@@ -227,27 +227,47 @@
 
 
     @staticmethod
-    def create_from_name(name, build, afe=None, tko=None, pool=None):
+    def create_fs_getter(autotest_dir):
+        """
+        @param autotest_dir: the place to find autotests.
+        @return a FileSystemGetter instance that looks under |autotest_dir|.
+        """
+        # currently hard-coded places to look for tests.
+        subpaths = ['server/site_tests', 'client/site_tests',
+                    'server/tests', 'client/tests']
+        directories = [os.path.join(autotest_dir, p) for p in subpaths]
+        return control_file_getter.FileSystemGetter(directories)
+
+
+    @staticmethod
+    def create_from_name(name, build, cf_getter=None,
+                         afe=None, tko=None, pool=None):
         """
         Create a Suite using a predicate based on the SUITE control file var.
 
         Makes a predicate based on |name| and uses it to instantiate a Suite
         that looks for tests in |autotest_dir| and will schedule them using
-        |afe|.  Results will be pulled from |tko| upon completion
+        |afe|.  Pulls control files from the default dev server.
+        Results will be pulled from |tko| upon completion.
 
         @param name: a value of the SUITE control file variable to search for.
         @param build: the build on which we're running this suite.
+        @param cf_getter: a control_file_getter.ControlFileGetter.
+                          If None, default to using a DevServerGetter.
         @param afe: an instance of AFE as defined in server/frontend.py.
         @param tko: an instance of TKO as defined in server/frontend.py.
         @param pool: Specify the pool of machines to use for scheduling
-                purposes.
+                     purposes.
         @return a Suite instance.
         """
+        if cf_getter is None:
+            cf_getter = Suite.create_ds_getter(build)
         return Suite(lambda t: hasattr(t, 'suite') and t.suite == name,
-                     name, build, afe, tko, pool)
+                     name, build, cf_getter, afe, tko, pool)
 
 
-    def __init__(self, predicate, tag, build, afe=None, tko=None,
+
+    def __init__(self, predicate, tag, build, cf_getter, afe=None, tko=None,
                  pool=None):
         """
         Constructor
@@ -257,6 +277,7 @@
                this Suite.
         @param tag: a string with which to tag jobs run in this suite.
         @param build: the build on which we're running this suite.
+        @param cf_getter: a control_file_getter.ControlFileGetter
         @param afe: an instance of AFE as defined in server/frontend.py.
         @param tko: an instance of TKO as defined in server/frontend.py.
         @param pool: Specify the pool of machines to use for scheduling
@@ -265,6 +286,7 @@
         self._predicate = predicate
         self._tag = tag
         self._build = build
+        self._cf_getter = cf_getter
         self._afe = afe or frontend_wrappers.RetryingAFE(timeout_min=30,
                                                          delay_sec=10,
                                                          debug=False)
@@ -273,9 +295,6 @@
                                                          debug=False)
         self._pool = pool
         self._jobs = []
-
-        self._cf_getter = Suite.create_cf_getter(self._build)
-
         self._tests = Suite.find_and_parse_tests(self._cf_getter,
                                                  self._predicate,
                                                  add_experimental=True)
diff --git a/server/cros/dynamic_suite_unittest.py b/server/cros/dynamic_suite_unittest.py
index bf8f0f5..2eb934e 100755
--- a/server/cros/dynamic_suite_unittest.py
+++ b/server/cros/dynamic_suite_unittest.py
@@ -318,7 +318,7 @@
         self.mock_control_file_parsing()
         self.mox.ReplayAll()
         suite = dynamic_suite.Suite.create_from_name(self._TAG, self.tmpdir,
-                                                     self.afe, self.tko)
+                                                     afe=self.afe, tko=self.tko)
 
         self.assertTrue(self.files['one'] in suite.tests)
         self.assertTrue(self.files['two'] in suite.tests)
@@ -352,7 +352,7 @@
 
         self.mox.ReplayAll()
         suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
-                                                     self.afe, self.tko)
+                                                     afe=self.afe, tko=self.tko)
         suite.schedule()
 
 
@@ -363,7 +363,7 @@
 
         self.mox.ReplayAll()
         suite = dynamic_suite.Suite.create_from_name(self._TAG, self._BUILD,
-                                                     self.afe, self.tko)
+                                                     afe=self.afe, tko=self.tko)
         suite.schedule(add_experimental=False)
 
 
@@ -381,12 +381,11 @@
 
         @return Suite object, after mocking out behavior needed to create it.
         """
-        self.mox.StubOutWithMock(dynamic_suite.Suite, 'create_cf_getter')
-        dynamic_suite.Suite.create_cf_getter(self.tmpdir).AndReturn(self.getter)
         self.expect_control_file_parsing()
         self.mox.ReplayAll()
         suite = dynamic_suite.Suite.create_from_name(self._TAG, self.tmpdir,
-                                                     self.afe, self.tko)
+                                                     self.getter, self.afe,
+                                                     self.tko)
         self.mox.ResetAll()
         return suite
 
diff --git a/site_utils/suite_enumerator.py b/site_utils/suite_enumerator.py
new file mode 100755
index 0000000..fac5987
--- /dev/null
+++ b/site_utils/suite_enumerator.py
@@ -0,0 +1,54 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Tool for enumerating the tests in a given suite.
+
+Given an autotest root directory and a suite name (e.g., bvt, regression), this
+tool will print out the name of each test in that suite, one per line.
+
+Example:
+$ ./site_utils/suite_enumerator.py -a /usr/local/autotest bvt 2>/dev/null
+login_LoginSuccess
+logging_CrashSender
+login_BadAuthentication
+
+This is intended for use only with Chrome OS test suits that leverage the
+dynamic suite infrastructure in server/cros/dynamic_suite.py.
+"""
+
+import optparse, os, sys, time
+import common
+from autotest_lib.server.cros import control_file_getter, dynamic_suite
+
+def parse_options():
+    usage = "usage: %prog [options] suite_name"
+    parser = optparse.OptionParser(usage=usage)
+    parser.add_option('-a', '--autotest_dir', dest='autotest_dir',
+                      default=os.path.abspath(
+                          os.path.join('..', os.path.dirname(__file__))),
+                      help='Directory under which to search for tests.'\
+                           ' (e.g. /usr/local/autotest)')
+    parser.add_option('-s', '--stable_only', dest='add_experimental',
+                      action='store_false', default=True,
+                      help='List only tests that are not labeled experimental.')
+    options, args = parser.parse_args()
+    return parser, options, args
+
+
+def main():
+    parser, options, args = parse_options()
+    if not args or len(args) != 1:
+        parser.print_help()
+        return
+
+    fs_getter = dynamic_suite.Suite.create_fs_getter(options.autotest_dir)
+    suite = dynamic_suite.Suite.create_from_name(args[0], '', fs_getter)
+    for test in suite.tests:
+        print test.name
+
+
+if __name__ == "__main__":
+    sys.exit(main())