Add a new devserver call to locate a file in build artifact

The new call `locate_file` looks up the given file name inside specified
build artifacts. One use case is to help caller to locate an apk file
inside a build artifact.

BUG=chromium:586320
TEST=local run devserver
curl
http://127.0.0.1:8082/locate_file?file_name=sl4a.apk\&target=shamu-userdebug\&build_id=2457013\&artifacts=test_zip\&branch=git_mnc-release\&os_type=android
expected retun: DATA/priv-app/sl4a/sl4a.apk
python devserver_integration_test.py

Change-Id: Ia029aae09b9bf52670c78fe2be4ccef62283ba41
Reviewed-on: https://chromium-review.googlesource.com/327278
Commit-Ready: Dan Shi <dshi@google.com>
Tested-by: Dan Shi <dshi@google.com>
Reviewed-by: Dan Shi <dshi@google.com>
diff --git a/artifact_info.py b/artifact_info.py
index 35fdba0..a8b0021 100644
--- a/artifact_info.py
+++ b/artifact_info.py
@@ -101,3 +101,8 @@
 REQUESTED_TO_OPTIONAL_MAP = {
     TEST_SUITES: [CONTROL_FILES, AUTOTEST_PACKAGES],
 }
+
+# Map between the artifact name and the folder after it's unzipped.
+ARTIFACT_UNZIP_FOLDER_MAP = {
+    ANDROID_TEST_ZIP: 'DATA',
+}
diff --git a/devserver.py b/devserver.py
index c5bd8bd..54c25e3 100755
--- a/devserver.py
+++ b/devserver.py
@@ -60,6 +60,7 @@
 from cherrypy.process import plugins
 
 import autoupdate
+import artifact_info
 import build_artifact
 import cherrypy_ext
 import common_util
@@ -759,6 +760,44 @@
     return 'Success'
 
   @cherrypy.expose
+  def locate_file(self, **kwargs):
+    """Get the path to the given file name.
+
+    This method looks up the given file name inside specified build artifacts.
+    One use case is to help caller to locate an apk file inside a build
+    artifact. The location of the apk file could be different based on the
+    branch and target.
+
+    Args:
+      file_name: Name of the file to look for.
+      artifacts: A list of artifact names to search for the file.
+
+    Returns:
+      Path to the file with the given name. It's relative to the folder for the
+      build, e.g., DATA/priv-app/sl4a/sl4a.apk
+
+    """
+    dl, _ = _get_downloader_and_factory(kwargs)
+    try:
+      file_name = kwargs['file_name'].lower()
+      artifacts = kwargs['artifacts']
+    except KeyError:
+      raise DevServerError('`file_name` and `artifacts` are required to search '
+                           'for a file in build artifacts.')
+    build_path = dl.GetBuildDir()
+    for artifact in artifacts:
+      # Get the unzipped folder of the artifact. If it's not defined in
+      # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
+      # directory directly.
+      folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
+      artifact_path = os.path.join(build_path, folder)
+      for root, _, filenames in os.walk(artifact_path):
+        if file_name in set([f.lower() for f in filenames]):
+          return os.path.relpath(os.path.join(root, file_name), build_path)
+    raise DevServerError('File `%s` can not be found in artifacts: %s' %
+                         (file_name, artifacts))
+
+  @cherrypy.expose
   def setup_telemetry(self, **kwargs):
     """Extracts and sets up telemetry
 
diff --git a/devserver_integration_test.py b/devserver_integration_test.py
index d37d62d..93b994c 100755
--- a/devserver_integration_test.py
+++ b/devserver_integration_test.py
@@ -152,7 +152,7 @@
     """Attempts to start devserver on |port|.
 
     In the default case where port == 0, the server will bind to an arbitrary
-    availble port. If successful, this method will set the devserver's pid
+    available port. If successful, this method will set the devserver's pid
     (self.pid), actual listening port (self.port) and URL (self.devserver_url).
 
     Raises:
@@ -418,9 +418,8 @@
     """Test that using a pidfile works correctly."""
     with open(self.pidfile, 'r') as f:
       pid = f.read()
-
     # Let's assert some process information about the devserver.
-    self.assertTrue(pid.isdigit())
+    self.assertTrue(pid.strip().isdigit())
     process = psutil.Process(int(pid))
     self.assertTrue(process.is_running())
     self.assertTrue('devserver.py' in process.cmdline)
@@ -438,7 +437,7 @@
     """Tests core autotest workflow where we stage/update with a test payload.
 
     """
-    build_id = 'x86-mario-release/R32-4810.0.0'
+    build_id = 'peppy-release/R50-7870.0.0'
     archive_url = 'gs://chromeos-image-archive/%s' % build_id
 
     response = self._MakeRPC(IS_STAGED, archive_url=archive_url,