[dev-util] Add stage_debug,symbolicate_dump endpoints to dev server

Add an endpoint to the dev server that will synchronously download
and stage the debug symbols for a given build.
Add an endpoint to the dev server that will symbolicate a minidump.

BUG=chromium-os:29850,chromium-os:30399
TEST=unit
TEST=run dev server, use curl to make it download some artifacts; ensure
TEST=debug.tgz is _not_ downloaded immediately, but that the rest of the build is staged.

TEST=run dev server, use curl to make it download debug_symbols; check to
TEST=see that debug symbols are staged in static/archive
TEST=once symbols are staged, run the dev server in your
TEST=chroot and use curl with a minidump file like this:
TEST=  curl -F minidump=@/home/cmasone/chromeos/phooey/powerd.20120424.141235.1005.dmp http://localhost:8080/symbolicate_dump

Change-Id: Ie460526396d2b9999137142c723b87793bc23aaa
Reviewed-on: https://gerrit.chromium.org/gerrit/21696
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Ready: Chris Masone <cmasone@chromium.org>
Tested-by: Chris Masone <cmasone@chromium.org>
diff --git a/downloader_unittest.py b/downloader_unittest.py
index 693689a..a470f84 100755
--- a/downloader_unittest.py
+++ b/downloader_unittest.py
@@ -12,7 +12,7 @@
 import tempfile
 import unittest
 
-import artifact_download
+import downloadable_artifact
 import devserver
 import devserver_util
 import downloader
@@ -26,7 +26,7 @@
 }
 
 
-class DownloaderTest(mox.MoxTestBase):
+class DownloaderTestBase(mox.MoxTestBase):
 
   def setUp(self):
     mox.MoxTestBase.setUp(self)
@@ -42,10 +42,10 @@
   def _CommonDownloaderSetup(self):
     """Common code to downloader tests.
 
-    Sets up artifacts and sets up expectations for synchronous artifacts to
-    be downloaded first.
+    Mocks out key devserver_util module methods, creates mock artifacts
+    and sets appropriate expectations.
 
-    Returns the artifacts to use in the test.
+    @return iterable of artifact objects with appropriate expectations.
     """
     board = 'x86-mario-release'
     self.mox.StubOutWithMock(devserver_util, 'AcquireLock')
@@ -53,32 +53,78 @@
     self.mox.StubOutWithMock(devserver_util, 'ReleaseLock')
     self.mox.StubOutWithMock(tempfile, 'mkdtemp')
 
-    artifacts = []
+    devserver_util.AcquireLock(
+        static_dir=self._work_dir,
+        tag=self._ClassUnderTest().GenerateLockTag(board, self.build)
+        ).AndReturn(self._work_dir)
 
+    tempfile.mkdtemp(suffix=mox.IgnoreArg()).AndReturn(self._work_dir)
+    return self._GenerateArtifacts()
+
+  def _CreateArtifactDownloader(self, artifacts):
+    """Create and return a Downloader of the appropriate type.
+
+    The returned downloader will expect to download and stage the
+    DownloadableArtifacts listed in [artifacts].
+
+    @param artifacts: iterable of DownloadableArtifacts.
+    @return instance of downloader.Downloader or subclass.
+    """
+    raise NotImplementedError()
+
+  def _ClassUnderTest(self):
+    """Return class object of the type being tested.
+
+    @return downloader.Downloader class object, or subclass.
+    """
+    raise NotImplementedError()
+
+  def _GenerateArtifacts(self):
+    """Instantiate artifact mocks and set expectations on them.
+
+    @return iterable of artifact objects with appropriate expectations.
+    """
+    raise NotImplementedError()
+
+
+class DownloaderTest(DownloaderTestBase):
+  """Unit tests for downloader.Downloader.
+
+  setUp() and tearDown() inherited from DownloaderTestBase.
+  """
+
+  def _CreateArtifactDownloader(self, artifacts):
+    d = downloader.Downloader(self._work_dir)
+    self.mox.StubOutWithMock(d, 'GatherArtifactDownloads')
+    d.GatherArtifactDownloads(
+        self._work_dir, self.archive_url_prefix, self.build,
+        self._work_dir).AndReturn(artifacts)
+    return d
+
+  def _ClassUnderTest(self):
+    return downloader.Downloader
+
+  def _GenerateArtifacts(self):
+    """Instantiate artifact mocks and set expectations on them.
+
+    Sets up artifacts and sets up expectations for synchronous artifacts to
+    be downloaded first.
+
+    @return iterable of artifact objects with appropriate expectations.
+    """
+    artifacts = []
     for index in range(5):
-      artifact = self.mox.CreateMock(artifact_download.DownloadableArtifact)
+      artifact = self.mox.CreateMock(downloadable_artifact.DownloadableArtifact)
       # Make every other artifact synchronous.
       if index % 2 == 0:
         artifact.Synchronous = lambda: True
+        artifact.Download()
+        artifact.Stage()
       else:
         artifact.Synchronous = lambda: False
 
       artifacts.append(artifact)
 
-    devserver_util.AcquireLock(
-        static_dir=self._work_dir,
-        tag='/'.join([board, self.build])).AndReturn(self._work_dir)
-
-    tempfile.mkdtemp(suffix=mox.IgnoreArg()).AndReturn(self._work_dir)
-    devserver_util.GatherArtifactDownloads(
-        self._work_dir, self.archive_url_prefix, self.build,
-        self._work_dir).AndReturn(artifacts)
-
-    for index, artifact in enumerate(artifacts):
-      if index % 2 == 0:
-        artifact.Download()
-        artifact.Stage()
-
     return artifacts
 
   def testDownloaderSerially(self):
@@ -91,9 +137,10 @@
         artifact.Download()
         artifact.Stage()
 
+    d = self._CreateArtifactDownloader(artifacts)
     self.mox.ReplayAll()
-    self.assertEqual(downloader.Downloader(self._work_dir).Download(
-        self.archive_url_prefix, background=False), 'Success')
+    self.assertEqual(d.Download(self.archive_url_prefix, background=False),
+                     'Success')
     self.mox.VerifyAll()
 
   def testDownloaderInBackground(self):
@@ -106,8 +153,8 @@
         artifact.Download()
         artifact.Stage()
 
+    d = self._CreateArtifactDownloader(artifacts)
     self.mox.ReplayAll()
-    d = downloader.Downloader(self._work_dir)
     d.Download(self.archive_url_prefix, background=True)
     self.assertEqual(d.GetStatusOfBackgroundDownloads(), 'Success')
     self.mox.VerifyAll()
@@ -115,6 +162,10 @@
   def testInteractionWithDevserver(self):
     """Tests interaction between the downloader and devserver methods."""
     artifacts = self._CommonDownloaderSetup()
+    devserver_util.GatherArtifactDownloads(
+        self._work_dir, self.archive_url_prefix, self.build,
+        self._work_dir).AndReturn(artifacts)
+
     class FakeUpdater():
       static_dir = self._work_dir
 
@@ -147,5 +198,45 @@
                                                        self._work_dir))
 
 
+class SymbolDownloaderTest(DownloaderTestBase):
+  """Unit tests for downloader.SymbolDownloader.
+
+  setUp() and tearDown() inherited from DownloaderTestBase.
+  """
+
+  def _CreateArtifactDownloader(self, artifacts):
+    d = downloader.SymbolDownloader(self._work_dir)
+    self.mox.StubOutWithMock(d, 'GatherArtifactDownloads')
+    d.GatherArtifactDownloads(
+        self._work_dir, self.archive_url_prefix, '',
+        self._work_dir).AndReturn(artifacts)
+    return d
+
+  def _ClassUnderTest(self):
+    return downloader.SymbolDownloader
+
+  def _GenerateArtifacts(self):
+    """Instantiate artifact mocks and set expectations on them.
+
+    Sets up a DebugTarball and sets up expectation that it will be
+    downloaded and staged.
+
+    @return iterable of one artifact object with appropriate expectations.
+    """
+    artifact = self.mox.CreateMock(downloadable_artifact.DownloadableArtifact)
+    artifact.Synchronous = lambda: True
+    artifact.Download()
+    artifact.Stage()
+    return [artifact]
+
+  def testDownloaderSerially(self):
+    """Runs through the symbol downloader workflow."""
+    d = self._CreateArtifactDownloader(self._CommonDownloaderSetup())
+
+    self.mox.ReplayAll()
+    self.assertEqual(d.Download(self.archive_url_prefix), 'Success')
+    self.mox.VerifyAll()
+
+
 if __name__ == '__main__':
   unittest.main()