[autotest] Pipe suite_name through client devserver code.

To reduce the overhead involved with fetching control files
from the devserver we generate a mapping of suite->control
files at build time. This cl will ask the control file getter
to fetch the control files for a given suite instead of all
the control files associated with an image.

Also removing SUITE = None from some networking test control
files, and patched unittests.

BUG=chromium:252398
TEST=Ran a suite with many control files. Made sure the suite->
     control file mapping falls back to the old code when a bad suite
     name is specified. Compared the test objects created in the old
     version vs those created with this change.
     Ran unittests.

CQ-DEPEND=CL:I6b590c8c40a863e6744875a26ac228ffd4dd8794

Change-Id: I4274c78764ffaaabef1ed48cdb40f523e307938d
Reviewed-on: https://gerrit.chromium.org/gerrit/63182
Tested-by: Prashanth Balasubramanian <beeps@chromium.org>
Reviewed-by: Dan Shi <dshi@chromium.org>
Commit-Queue: Prashanth Balasubramanian <beeps@chromium.org>
diff --git a/client/common_lib/cros/dev_server.py b/client/common_lib/cros/dev_server.py
index 7608539..806c6c9 100644
--- a/client/common_lib/cros/dev_server.py
+++ b/client/common_lib/cros/dev_server.py
@@ -478,16 +478,19 @@
 
 
     @remote_devserver_call()
-    def list_control_files(self, build):
+    def list_control_files(self, build, suite_name=''):
         """Ask the devserver to list all control files for |build|.
 
         @param build: The build (e.g. x86-mario-release/R18-1586.0.0-a1-b1514)
                       whose control files the caller wants listed.
+        @param suite_name: The name of the suite for which we require control
+                           files.
         @return None on failure, or a list of control file paths
                 (e.g. server/site_tests/autoupdate/control)
         @raise DevServerException upon any return code that's not HTTP OK.
         """
-        call = self.build_call('controlfiles', build=build)
+        call = self.build_call('controlfiles', build=build,
+                               suite_name=suite_name)
         response = urllib2.urlopen(call)
         return [line.rstrip() for line in response]
 
diff --git a/client/site_tests/network_NavigateToUrl/control b/client/site_tests/network_NavigateToUrl/control
index 69abbc3..36548b1 100644
--- a/client/site_tests/network_NavigateToUrl/control
+++ b/client/site_tests/network_NavigateToUrl/control
@@ -8,7 +8,6 @@
 CRITERIA = """
 This test will fail if it does not successfully navigate to the indicated url.
 """
-SUITE = "None"
 TIME = "SHORT"
 TEST_CATEGORY = "Functional"
 TEST_CLASS = "network"
diff --git a/client/site_tests/network_WiFiDownloads/control b/client/site_tests/network_WiFiDownloads/control
index da6570b..8bb67d2 100644
--- a/client/site_tests/network_WiFiDownloads/control
+++ b/client/site_tests/network_WiFiDownloads/control
@@ -9,7 +9,6 @@
 This test will fail if it cannot connect to a wireless network and
 download files.
 """
-SUITE = "None"
 TIME = "SHORT"
 TEST_CATEGORY = "Functional"
 TEST_CLASS = "network"
diff --git a/client/site_tests/network_WiFiSimpleConnection/control b/client/site_tests/network_WiFiSimpleConnection/control
index a50cd82..28ec6ed 100644
--- a/client/site_tests/network_WiFiSimpleConnection/control
+++ b/client/site_tests/network_WiFiSimpleConnection/control
@@ -9,7 +9,6 @@
 This test will fail if it cannot connect to a wireless network and navigate
 to www.msn.com.
 """
-SUITE = "None"
 TIME = "SHORT"
 TEST_CATEGORY = "Functional"
 TEST_CLASS = "network"
diff --git a/server/cros/dynamic_suite/control_file_getter.py b/server/cros/dynamic_suite/control_file_getter.py
index 89e1108..7309838 100644
--- a/server/cros/dynamic_suite/control_file_getter.py
+++ b/server/cros/dynamic_suite/control_file_getter.py
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 import common
-import os, re
+import logging, os, re
 from autotest_lib.client.common_lib import error, utils
 from autotest_lib.client.common_lib.cros import dev_server
 
@@ -20,10 +20,11 @@
         pass
 
 
-    def get_control_file_list(self):
+    def get_control_file_list(self, suite_name=''):
         """
         Gather a list of paths to control files.
 
+        @param suite_name: The name of a suite we would like control files for.
         @return A list of file paths.
         @throws NoControlFileList if there is an error while listing.
         """
@@ -63,17 +64,18 @@
         self._files = []
 
 
-    def get_control_file_list(self):
+    def get_control_file_list(self, suite_name=''):
         """
         Gather a list of paths to control files.
 
         Gets a list of control files; populates |self._files| with that list
         and then returns the paths to all useful and wanted files in the list.
 
+        @param suite_name: The name of a suite we would like control files for.
         @return A list of file paths.
         @throws NoControlFileList if there is an error while listing.
         """
-        files = self._get_control_file_list()
+        files = self._get_control_file_list(suite_name=suite_name)
         for cf_filter in self.CONTROL_FILE_FILTERS:
           files = filter(lambda path: not path.endswith(cf_filter), files)
         self._files = files
@@ -128,7 +130,7 @@
         return '__init__.py' not in name and '.svn' not in name
 
 
-    def _get_control_file_list(self):
+    def _get_control_file_list(self, suite_name=''):
         """
         Gather a list of paths to control files under |self._paths|.
 
@@ -136,9 +138,16 @@
         |self._CONTROL_PATTERN|.  Populates |self._files| with that list
         and then returns the paths to all useful files in the list.
 
+        @param suite_name: The name of a suite we would like control files for.
         @return A list of files that match |self._CONTROL_PATTERN|.
         @throws NoControlFileList if we find no files.
         """
+        if suite_name:
+            logging.warning('Getting control files for a specific suite has '
+                            'not been implemented for FileSystemGetter. '
+                            'Getting all control files instead.')
+
+
         regexp = re.compile(self._CONTROL_PATTERN)
         directories = self._paths
         while len(directories) > 0:
@@ -201,19 +210,23 @@
         return DevServerGetter(build, ds)
 
 
-    def _get_control_file_list(self):
+    def _get_control_file_list(self, suite_name=''):
         """
         Gather a list of paths to control files from |self._dev_server|.
 
         Get a listing of all the control files for |self._build| on
         |self._dev_server|.  Populates |self._files| with that list
-        and then returns paths (under the autotest dir) to them.
+        and then returns paths (under the autotest dir) to them. If suite_name
+        is specified, this method populates |self._files| with the control
+        files from just the specified suite.
 
+        @param suite_name: The name of a suite we would like control files for.
         @return A list of control file paths.
         @throws NoControlFileList if there is an error while listing.
         """
         try:
-            return self._dev_server.list_control_files(self._build)
+            return self._dev_server.list_control_files(self._build,
+                                                       suite_name=suite_name)
         except dev_server.DevServerException as e:
             raise error.NoControlFileList(e)
 
diff --git a/server/cros/dynamic_suite/control_file_getter_unittest.py b/server/cros/dynamic_suite/control_file_getter_unittest.py
index cc0684e..ad1049d 100644
--- a/server/cros/dynamic_suite/control_file_getter_unittest.py
+++ b/server/cros/dynamic_suite/control_file_getter_unittest.py
@@ -37,7 +37,9 @@
 
     def testListControlFiles(self):
         """Should successfully list control files from the dev server."""
-        self.dev_server.list_control_files(self._BUILD).AndReturn(self._FILES)
+        self.dev_server.list_control_files(
+                self._BUILD,
+                suite_name='').AndReturn(self._FILES)
         self.mox.ReplayAll()
         self.assertEquals(self.getter.get_control_file_list(), self._FILES)
         self.assertEquals(self.getter._files, self._FILES)
@@ -45,7 +47,9 @@
 
     def testListControlFilesFail(self):
         """Should fail to list control files from the dev server."""
-        self.dev_server.list_control_files(self._BUILD).AndRaise(self._403)
+        self.dev_server.list_control_files(
+                self._BUILD,
+                suite_name='').AndRaise(self._403)
         self.mox.ReplayAll()
         self.assertRaises(error.NoControlFileList,
                           self.getter.get_control_file_list)
@@ -94,7 +98,9 @@
         path = "file/%s/control" % name
 
         files = self._FILES + [path]
-        self.dev_server.list_control_files(self._BUILD).AndReturn(files)
+        self.dev_server.list_control_files(
+                self._BUILD,
+                suite_name='').AndReturn(files)
         self.dev_server.get_control_file(self._BUILD,
                                          path).AndReturn(self._CONTENTS)
         self.mox.ReplayAll()
@@ -110,7 +116,9 @@
         path = "file/" + name
 
         files = self._FILES + [path]
-        self.dev_server.list_control_files(self._BUILD).AndReturn(files)
+        self.dev_server.list_control_files(
+                self._BUILD,
+                suite_name='').AndReturn(files)
         self.dev_server.get_control_file(self._BUILD,
                                          path).AndReturn(self._CONTENTS)
         self.mox.ReplayAll()
@@ -122,7 +130,9 @@
         """Should fail to get a control file from the dev server by name."""
         name = 'one'
 
-        self.dev_server.list_control_files(self._BUILD).AndReturn(self._FILES)
+        self.dev_server.list_control_files(
+                self._BUILD,
+                suite_name='').AndReturn(self._FILES)
         self.mox.ReplayAll()
         self.assertRaises(error.ControlFileNotFound,
                           self.getter.get_control_file_contents_by_name,
diff --git a/server/cros/dynamic_suite/suite.py b/server/cros/dynamic_suite/suite.py
index d5b288f..d2ca1fe 100644
--- a/server/cros/dynamic_suite/suite.py
+++ b/server/cros/dynamic_suite/suite.py
@@ -277,6 +277,7 @@
         self._jobs = []
         self._tests = Suite.find_and_parse_tests(self._cf_getter,
                                                  self._predicate,
+                                                 self._tag,
                                                  add_experimental=True)
         self._max_runtime_mins = max_runtime_mins
         self._version_prefix = version_prefix
@@ -530,18 +531,28 @@
 
 
     @staticmethod
-    def find_and_parse_tests(cf_getter, predicate, add_experimental=False):
+    def find_and_parse_tests(cf_getter, predicate, suite_name='',
+                             add_experimental=False):
         """
         Function to scan through all tests and find eligible tests.
 
         Looks at control files returned by _cf_getter.get_control_file_list()
-        for tests that pass self._predicate().
+        for tests that pass self._predicate(). When this method is called
+        with a file system ControlFileGetter, it performs a full parse of the
+        root directory associated with the getter. This is the case when it's
+        invoked from suite_preprocessor. When it's invoked with a devserver
+        getter it looks up the suite_name in a suite to control file map
+        generated at build time, and parses the relevant control files alone.
+        This lookup happens on the devserver, so as far as this method is
+        concerned, both cases are equivalent.
 
         @param cf_getter: a control_file_getter.ControlFileGetter used to list
                and fetch the content of control files
         @param predicate: a function that should return True when run over a
                ControlData representation of a control file that should be in
                this Suite.
+        @param suite_name: If specified, this method will attempt to restrain
+                           the search space to just this suite's control files.
         @param add_experimental: add tests with experimental attribute set.
 
         @return list of ControlData objects that should be run, with control
@@ -549,7 +560,8 @@
                 on the TIME setting in control file, slowest test comes first.
         """
         tests = {}
-        files = cf_getter.get_control_file_list()
+        files = cf_getter.get_control_file_list(suite_name=suite_name)
+
         matcher = re.compile(r'[^/]+/(deps|profilers)/.+')
         parsed_count = 0
         for file in filter(lambda f: not matcher.match(f), files):
diff --git a/server/cros/dynamic_suite/suite_unittest.py b/server/cros/dynamic_suite/suite_unittest.py
index 5714d9d..6f0d223 100644
--- a/server/cros/dynamic_suite/suite_unittest.py
+++ b/server/cros/dynamic_suite/suite_unittest.py
@@ -73,15 +73,19 @@
         shutil.rmtree(self.tmpdir, ignore_errors=True)
 
 
-    def expect_control_file_parsing(self):
-        """Expect an attempt to parse the 'control files' in |self.files|."""
+    def expect_control_file_parsing(self, suite_name=_TAG):
+        """Expect an attempt to parse the 'control files' in |self.files|.
+
+        @param suite_name: The suite name to parse control files for.
+        """
         all_files = self.files.keys() + self.files_to_filter.keys()
         self._set_control_file_parsing_expectations(False, all_files,
-                                                    self.files)
+                                                    self.files, suite_name)
 
 
     def _set_control_file_parsing_expectations(self, already_stubbed,
-                                               file_list, files_to_parse):
+                                               file_list, files_to_parse,
+                                               suite_name):
         """Expect an attempt to parse the 'control files' in |files|.
 
         @param already_stubbed: parse_control_string already stubbed out.
@@ -92,7 +96,8 @@
         if not already_stubbed:
             self.mox.StubOutWithMock(control_data, 'parse_control_string')
 
-        self.getter.get_control_file_list().AndReturn(file_list)
+        self.getter.get_control_file_list(
+            suite_name=suite_name).AndReturn(file_list)
         for file, data in files_to_parse.iteritems():
             self.getter.get_control_file_contents(
                 file).InAnyOrder().AndReturn(data.string)
@@ -106,7 +111,7 @@
         self.mox.ReplayAll()
 
         predicate = lambda d: d.text == self.files['two'].string
-        tests = Suite.find_and_parse_tests(self.getter, predicate)
+        tests = Suite.find_and_parse_tests(self.getter, predicate, self._TAG)
         self.assertEquals(len(tests), 1)
         self.assertEquals(tests[0], self.files['two'])
 
@@ -119,6 +124,7 @@
         predicate = lambda d: d.suite == self._TAG
         tests = Suite.find_and_parse_tests(self.getter,
                                            predicate,
+                                           self._TAG,
                                            add_experimental=True)
         self.assertEquals(len(tests), 5)
         self.assertTrue(self.files['one'] in tests)
@@ -131,7 +137,7 @@
     def testAdHocSuiteCreation(self):
         """Should be able to schedule an ad-hoc suite by specifying
         a single test name."""
-        self.expect_control_file_parsing()
+        self.expect_control_file_parsing(suite_name='ad_hoc_suite')
         self.mox.ReplayAll()
         predicate = Suite.test_name_equals_predicate('name-data_five')
         suite = Suite.create_from_predicates([predicate], self._BUILD,
@@ -193,6 +199,7 @@
         Suite.find_and_parse_tests(
             mox.IgnoreArg(),
             mox.IgnoreArg(),
+            mox.IgnoreArg(),
             add_experimental=True).AndReturn(self.files.values())
 
 
@@ -390,6 +397,7 @@
         # Get all tests.
         tests = Suite.find_and_parse_tests(self.getter,
                                            lambda d: True,
+                                           self._TAG,
                                            add_experimental=True)
         self.assertEquals(len(tests), 6)
         times = [control_data.ControlData.get_test_time_index(test.time)
diff --git a/site_utils/suite_preprocessor.py b/site_utils/suite_preprocessor.py
index c5c0cdd..d2a2131 100755
--- a/site_utils/suite_preprocessor.py
+++ b/site_utils/suite_preprocessor.py
@@ -51,7 +51,8 @@
     """
     fs_getter = Suite.create_fs_getter(autotest_dir)
     predicate = lambda t: hasattr(t, 'suite')
-    return Suite.find_and_parse_tests(fs_getter, predicate, True)
+    return Suite.find_and_parse_tests(fs_getter, predicate,
+                                      add_experimental=True)
 
 
 def calculate_dependencies(autotest_dir):