dependency: Get a list of relevant packages based on src_paths.

For a given board, get a list of all package dependencies based on the
given paths.

BUG=chromium:1111319
TEST=./run_pytest

Cq-Depend: 2347449
Change-Id: I6f5a9b89da3e1fc5134a8aacd4ec8386f22ec3fa
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/2347393
Commit-Queue: Navil Perez <navil@google.com>
Tested-by: Navil Perez <navil@google.com>
Reviewed-by: Alex Klein <saklein@chromium.org>
diff --git a/api/controller/dependency.py b/api/controller/dependency.py
index fd3a434..fcf9750 100644
--- a/api/controller/dependency.py
+++ b/api/controller/dependency.py
@@ -13,9 +13,11 @@
 
 import sys
 
+from chromite.api import api_config
 from chromite.api import faux
 from chromite.api import validate
 from chromite.api.controller import controller_util
+from chromite.api.gen.chromite.api import depgraph_pb2
 # TODO(crbug/1081828): stop using build_target and drop it from the proto.
 from chromite.lib import cros_build_lib
 from chromite.lib import portage_util
@@ -92,6 +94,45 @@
   AugmentDepGraphProtoFromJsonMap(sdk_json_map, output_proto.sdk_dep_graph)
 
 
+def _ListResponse(_input_proto, output_proto, _config):
+  """Add fake dependency data to a successful response."""
+  package_dep = output_proto.package_deps.add()
+  package_dep.category = 'category'
+  package_dep.package_name = 'name'
+
+
+@faux.success(_ListResponse)
+@faux.empty_error
+@validate.require('sysroot.build_target.name')
+@validate.exists('sysroot.path')
+@validate.validation_complete
+def List(input_proto: depgraph_pb2.ListRequest,
+         output_proto: depgraph_pb2.ListResponse,
+         _config: api_config.ApiConfig):
+  """Get a list of package dependencies.
+
+  Args:
+    input_proto: The input arguments message.
+    output_proto: The empty output message.
+    _config: The API call config.
+  """
+  build_target = controller_util.ParseBuildTarget(
+      input_proto.sysroot.build_target)
+  sysroot_path = input_proto.sysroot.path
+  src_paths = [src_path.path for src_path in input_proto.src_paths]
+  packages = [controller_util.PackageInfoToCPV(x) for x in input_proto.packages]
+
+  package_deps = dependency.GetDependencies(
+      sysroot_path,
+      build_target=build_target,
+      src_paths=src_paths,
+      packages=packages)
+  for package in package_deps:
+    package_info = output_proto.package_deps.add()
+    cpv = portage_util.SplitCPV(package, strict=False)
+    controller_util.CPVToPackageInfo(cpv, package_info)
+
+
 def _DummyGetToolchainPathsResponse(_input_proto, output_proto, _config):
   """Create a fake successful response for GetToolchainPaths."""
   dummy_entry = output_proto.paths.add()
diff --git a/api/controller/dependency_unittest.py b/api/controller/dependency_unittest.py
index 9e97b78..455bd9b 100644
--- a/api/controller/dependency_unittest.py
+++ b/api/controller/dependency_unittest.py
@@ -6,14 +6,18 @@
 
 from __future__ import print_function
 
+import os
 import sys
 
 from chromite.api import api_config
 from chromite.api.controller import controller_util
 from chromite.api.controller import dependency
 from chromite.api.gen.chromite.api import depgraph_pb2
+from chromite.api.gen.chromite.api import sysroot_pb2
 from chromite.api.gen.chromiumos import common_pb2
+from chromite.lib import cros_build_lib
 from chromite.lib import cros_test_lib
+from chromite.lib import osutils
 from chromite.service import dependency as dependency_service
 
 pytestmark = cros_test_lib.pytestmark_inside_only
@@ -46,7 +50,7 @@
                 'action': 'merge',
                 'category': 'troop',
                 'cpes': [],
-                'deps': [],
+                'deps': ['equipment/jetpack'],
                 'rev_deps': ['commander/darthvader'],
                 'full_name': 'troop/clone-1.2.3',
                 'name': 'clone',
@@ -62,11 +66,22 @@
                 'name': 'robot',
                 'version': '2.3.4'
             },
+            'equipment/jetpack-3.4.5': {
+                'action': 'merge',
+                'category': 'equipment',
+                'cpes': [],
+                'deps': [],
+                'rev_deps': ['commander/darthvader'],
+                'full_name': 'equipment/jetpack-3.4.5',
+                'name': 'jetpack',
+                'version': '3.4.5'
+            },
         },
         'source_path_mapping': {
             'commander/darthvader-1.49.3.3': ['/control/room'],
             'troop/clone-1.2.3': ['/bunker'],
             'troop/robot-2.3.4': ['/factory'],
+            'equipment/jetpack-3.4.5': ['/factory'],
         },
     }
 
@@ -141,3 +156,61 @@
                                        self.mock_call_config)
     patch.assert_not_called()
     self.assertEqual(self.response.dep_graph.build_target.name, 'target_board')
+
+
+class ListTest(cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin):
+  """Unittests for the List endpoint."""
+
+  def setUp(self):
+    self.response = depgraph_pb2.ListResponse()
+    self.build_target = common_pb2.BuildTarget(name='target')
+    self.sysroot = os.path.join(self.tempdir, 'target')
+    osutils.SafeMakedirs(self.sysroot)
+
+  def testValidateOnly(self):
+    """Sanity check that a validate only call does not execute any logic."""
+    sysroot = sysroot_pb2.Sysroot(
+        path=self.sysroot, build_target=self.build_target)
+    input_proto = depgraph_pb2.ListRequest(sysroot=sysroot)
+    dependency.List(input_proto, self.response, self.validate_only_config)
+
+  def testArgumentValidationMissingSysrootPath(self):
+    """Test missing sysroot path."""
+    sysroot = sysroot_pb2.Sysroot(build_target=self.build_target)
+    input_proto = depgraph_pb2.ListRequest(sysroot=sysroot)
+    with self.assertRaises(cros_build_lib.DieSystemExit):
+      dependency.List(input_proto, self.response, self.api_config)
+
+  def testArgumentValidationMissingBuildTarget(self):
+    """Test missing build target name."""
+    sysroot = sysroot_pb2.Sysroot(
+        path=self.sysroot, build_target=common_pb2.BuildTarget())
+    input_proto = depgraph_pb2.ListRequest(sysroot=sysroot)
+    with self.assertRaises(cros_build_lib.DieSystemExit):
+      dependency.List(input_proto, self.response, self.api_config)
+
+  def testListResponse(self):
+    """Test calls helper method with correct args."""
+    mock_get_deps = self.PatchObject(
+        dependency_service, 'GetDependencies', return_value=['foo/bar-1.2.3'])
+    sysroot = sysroot_pb2.Sysroot(
+        path=self.sysroot, build_target=self.build_target)
+    path = '/path'
+    package = common_pb2.PackageInfo(category='foo', package_name='bar')
+    input_proto = depgraph_pb2.ListRequest(
+        sysroot=sysroot,
+        src_paths=[
+            depgraph_pb2.SourcePath(path='/path'),
+        ],
+        packages=[package])
+    dependency.List(input_proto, self.response, self.api_config)
+    mock_get_deps.assert_called_once_with(
+        self.sysroot,
+        build_target=controller_util.ParseBuildTarget(self.build_target),
+        src_paths=[path],
+        packages=[controller_util.PackageInfoToCPV(package)])
+    expected_deps = [
+        common_pb2.PackageInfo(
+            category='foo', package_name='bar', version='1.2.3')
+    ]
+    self.assertCountEqual(expected_deps, self.response.package_deps)
diff --git a/api/gen/chromite/api/depgraph_pb2.py b/api/gen/chromite/api/depgraph_pb2.py
index 47fd1b8..4e8b193 100644
--- a/api/gen/chromite/api/depgraph_pb2.py
+++ b/api/gen/chromite/api/depgraph_pb2.py
@@ -22,7 +22,7 @@
   package='chromite.api',
   syntax='proto3',
   serialized_options=_b('Z6go.chromium.org/chromiumos/infra/proto/go/chromite/api'),
-  serialized_pb=_b('\n\x1b\x63hromite/api/depgraph.proto\x12\x0c\x63hromite.api\x1a\x1c\x63hromite/api/build_api.proto\x1a\x1a\x63hromite/api/sysroot.proto\x1a\x17\x63hromiumos/common.proto\"\x95\x01\n\x08\x44\x65pGraph\x12-\n\x0c\x62uild_target\x18\x01 \x01(\x0b\x32\x17.chromiumos.BuildTarget\x12\x32\n\x0cpackage_deps\x18\x02 \x03(\x0b\x32\x1c.chromite.api.PackageDepInfo\x12&\n\x07sysroot\x18\x03 \x01(\x0b\x32\x15.chromite.api.Sysroot\"\xb0\x01\n\x0ePackageDepInfo\x12-\n\x0cpackage_info\x18\x01 \x01(\x0b\x32\x17.chromiumos.PackageInfo\x12\x34\n\x13\x64\x65pendency_packages\x18\x02 \x03(\x0b\x32\x17.chromiumos.PackageInfo\x12\x39\n\x17\x64\x65pendency_source_paths\x18\x03 \x03(\x0b\x32\x18.chromite.api.SourcePath\"\x1a\n\nSourcePath\x12\x0c\n\x04path\x18\x01 \x01(\t\"\xc6\x01\n\x1eGetBuildDependencyGraphRequest\x12&\n\x07sysroot\x18\x04 \x01(\x0b\x32\x15.chromite.api.Sysroot\x12-\n\x0c\x62uild_target\x18\x01 \x01(\x0b\x32\x17.chromiumos.BuildTarget\x12\"\n\x06\x63hroot\x18\x02 \x01(\x0b\x32\x12.chromiumos.Chroot\x12)\n\x08packages\x18\x03 \x03(\x0b\x32\x17.chromiumos.PackageInfo\"{\n\x1fGetBuildDependencyGraphResponse\x12)\n\tdep_graph\x18\x01 \x01(\x0b\x32\x16.chromite.api.DepGraph\x12-\n\rsdk_dep_graph\x18\x02 \x01(\x0b\x32\x16.chromite.api.DepGraph\">\n\x18GetToolchainPathsRequest\x12\"\n\x06\x63hroot\x18\x01 \x01(\x0b\x32\x12.chromiumos.Chroot\"D\n\x19GetToolchainPathsResponse\x12\'\n\x05paths\x18\x01 \x03(\x0b\x32\x18.chromite.api.SourcePath2\x85\x02\n\x11\x44\x65pendencyService\x12v\n\x17GetBuildDependencyGraph\x12,.chromite.api.GetBuildDependencyGraphRequest\x1a-.chromite.api.GetBuildDependencyGraphResponse\x12\x64\n\x11GetToolchainPaths\x12&.chromite.api.GetToolchainPathsRequest\x1a\'.chromite.api.GetToolchainPathsResponse\x1a\x12\xc2\xed\x1a\x0e\n\ndependency\x10\x01\x42\x38Z6go.chromium.org/chromiumos/infra/proto/go/chromite/apib\x06proto3')
+  serialized_pb=_b('\n\x1b\x63hromite/api/depgraph.proto\x12\x0c\x63hromite.api\x1a\x1c\x63hromite/api/build_api.proto\x1a\x1a\x63hromite/api/sysroot.proto\x1a\x17\x63hromiumos/common.proto\"\x95\x01\n\x08\x44\x65pGraph\x12-\n\x0c\x62uild_target\x18\x01 \x01(\x0b\x32\x17.chromiumos.BuildTarget\x12\x32\n\x0cpackage_deps\x18\x02 \x03(\x0b\x32\x1c.chromite.api.PackageDepInfo\x12&\n\x07sysroot\x18\x03 \x01(\x0b\x32\x15.chromite.api.Sysroot\"\xb0\x01\n\x0ePackageDepInfo\x12-\n\x0cpackage_info\x18\x01 \x01(\x0b\x32\x17.chromiumos.PackageInfo\x12\x34\n\x13\x64\x65pendency_packages\x18\x02 \x03(\x0b\x32\x17.chromiumos.PackageInfo\x12\x39\n\x17\x64\x65pendency_source_paths\x18\x03 \x03(\x0b\x32\x18.chromite.api.SourcePath\"\x1a\n\nSourcePath\x12\x0c\n\x04path\x18\x01 \x01(\t\"\xc6\x01\n\x1eGetBuildDependencyGraphRequest\x12&\n\x07sysroot\x18\x04 \x01(\x0b\x32\x15.chromite.api.Sysroot\x12-\n\x0c\x62uild_target\x18\x01 \x01(\x0b\x32\x17.chromiumos.BuildTarget\x12\"\n\x06\x63hroot\x18\x02 \x01(\x0b\x32\x12.chromiumos.Chroot\x12)\n\x08packages\x18\x03 \x03(\x0b\x32\x17.chromiumos.PackageInfo\"{\n\x1fGetBuildDependencyGraphResponse\x12)\n\tdep_graph\x18\x01 \x01(\x0b\x32\x16.chromite.api.DepGraph\x12-\n\rsdk_dep_graph\x18\x02 \x01(\x0b\x32\x16.chromite.api.DepGraph\">\n\x18GetToolchainPathsRequest\x12\"\n\x06\x63hroot\x18\x01 \x01(\x0b\x32\x12.chromiumos.Chroot\"D\n\x19GetToolchainPathsResponse\x12\'\n\x05paths\x18\x01 \x03(\x0b\x32\x18.chromite.api.SourcePath\"\xb1\x01\n\x0bListRequest\x12&\n\x07sysroot\x18\x01 \x01(\x0b\x32\x15.chromite.api.Sysroot\x12\"\n\x06\x63hroot\x18\x02 \x01(\x0b\x32\x12.chromiumos.Chroot\x12+\n\tsrc_paths\x18\x03 \x03(\x0b\x32\x18.chromite.api.SourcePath\x12)\n\x08packages\x18\x04 \x03(\x0b\x32\x17.chromiumos.PackageInfo\"=\n\x0cListResponse\x12-\n\x0cpackage_deps\x18\x01 \x03(\x0b\x32\x17.chromiumos.PackageInfo2\xc4\x02\n\x11\x44\x65pendencyService\x12v\n\x17GetBuildDependencyGraph\x12,.chromite.api.GetBuildDependencyGraphRequest\x1a-.chromite.api.GetBuildDependencyGraphResponse\x12\x64\n\x11GetToolchainPaths\x12&.chromite.api.GetToolchainPathsRequest\x1a\'.chromite.api.GetToolchainPathsResponse\x12=\n\x04List\x12\x19.chromite.api.ListRequest\x1a\x1a.chromite.api.ListResponse\x1a\x12\xc2\xed\x1a\x0e\n\ndependency\x10\x01\x42\x38Z6go.chromium.org/chromiumos/infra/proto/go/chromite/apib\x06proto3')
   ,
   dependencies=[chromite_dot_api_dot_build__api__pb2.DESCRIPTOR,chromite_dot_api_dot_sysroot__pb2.DESCRIPTOR,chromiumos_dot_common__pb2.DESCRIPTOR,])
 
@@ -301,6 +301,89 @@
   serialized_end=945,
 )
 
+
+_LISTREQUEST = _descriptor.Descriptor(
+  name='ListRequest',
+  full_name='chromite.api.ListRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='sysroot', full_name='chromite.api.ListRequest.sysroot', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='chroot', full_name='chromite.api.ListRequest.chroot', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='src_paths', full_name='chromite.api.ListRequest.src_paths', index=2,
+      number=3, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='packages', full_name='chromite.api.ListRequest.packages', index=3,
+      number=4, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=948,
+  serialized_end=1125,
+)
+
+
+_LISTRESPONSE = _descriptor.Descriptor(
+  name='ListResponse',
+  full_name='chromite.api.ListResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='package_deps', full_name='chromite.api.ListResponse.package_deps', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1127,
+  serialized_end=1188,
+)
+
 _DEPGRAPH.fields_by_name['build_target'].message_type = chromiumos_dot_common__pb2._BUILDTARGET
 _DEPGRAPH.fields_by_name['package_deps'].message_type = _PACKAGEDEPINFO
 _DEPGRAPH.fields_by_name['sysroot'].message_type = chromite_dot_api_dot_sysroot__pb2._SYSROOT
@@ -315,6 +398,11 @@
 _GETBUILDDEPENDENCYGRAPHRESPONSE.fields_by_name['sdk_dep_graph'].message_type = _DEPGRAPH
 _GETTOOLCHAINPATHSREQUEST.fields_by_name['chroot'].message_type = chromiumos_dot_common__pb2._CHROOT
 _GETTOOLCHAINPATHSRESPONSE.fields_by_name['paths'].message_type = _SOURCEPATH
+_LISTREQUEST.fields_by_name['sysroot'].message_type = chromite_dot_api_dot_sysroot__pb2._SYSROOT
+_LISTREQUEST.fields_by_name['chroot'].message_type = chromiumos_dot_common__pb2._CHROOT
+_LISTREQUEST.fields_by_name['src_paths'].message_type = _SOURCEPATH
+_LISTREQUEST.fields_by_name['packages'].message_type = chromiumos_dot_common__pb2._PACKAGEINFO
+_LISTRESPONSE.fields_by_name['package_deps'].message_type = chromiumos_dot_common__pb2._PACKAGEINFO
 DESCRIPTOR.message_types_by_name['DepGraph'] = _DEPGRAPH
 DESCRIPTOR.message_types_by_name['PackageDepInfo'] = _PACKAGEDEPINFO
 DESCRIPTOR.message_types_by_name['SourcePath'] = _SOURCEPATH
@@ -322,6 +410,8 @@
 DESCRIPTOR.message_types_by_name['GetBuildDependencyGraphResponse'] = _GETBUILDDEPENDENCYGRAPHRESPONSE
 DESCRIPTOR.message_types_by_name['GetToolchainPathsRequest'] = _GETTOOLCHAINPATHSREQUEST
 DESCRIPTOR.message_types_by_name['GetToolchainPathsResponse'] = _GETTOOLCHAINPATHSRESPONSE
+DESCRIPTOR.message_types_by_name['ListRequest'] = _LISTREQUEST
+DESCRIPTOR.message_types_by_name['ListResponse'] = _LISTRESPONSE
 _sym_db.RegisterFileDescriptor(DESCRIPTOR)
 
 DepGraph = _reflection.GeneratedProtocolMessageType('DepGraph', (_message.Message,), dict(
@@ -373,6 +463,20 @@
   ))
 _sym_db.RegisterMessage(GetToolchainPathsResponse)
 
+ListRequest = _reflection.GeneratedProtocolMessageType('ListRequest', (_message.Message,), dict(
+  DESCRIPTOR = _LISTREQUEST,
+  __module__ = 'chromite.api.depgraph_pb2'
+  # @@protoc_insertion_point(class_scope:chromite.api.ListRequest)
+  ))
+_sym_db.RegisterMessage(ListRequest)
+
+ListResponse = _reflection.GeneratedProtocolMessageType('ListResponse', (_message.Message,), dict(
+  DESCRIPTOR = _LISTRESPONSE,
+  __module__ = 'chromite.api.depgraph_pb2'
+  # @@protoc_insertion_point(class_scope:chromite.api.ListResponse)
+  ))
+_sym_db.RegisterMessage(ListResponse)
+
 
 DESCRIPTOR._options = None
 
@@ -382,8 +486,8 @@
   file=DESCRIPTOR,
   index=0,
   serialized_options=_b('\302\355\032\016\n\ndependency\020\001'),
-  serialized_start=948,
-  serialized_end=1209,
+  serialized_start=1191,
+  serialized_end=1515,
   methods=[
   _descriptor.MethodDescriptor(
     name='GetBuildDependencyGraph',
@@ -403,6 +507,15 @@
     output_type=_GETTOOLCHAINPATHSRESPONSE,
     serialized_options=None,
   ),
+  _descriptor.MethodDescriptor(
+    name='List',
+    full_name='chromite.api.DependencyService.List',
+    index=2,
+    containing_service=None,
+    input_type=_LISTREQUEST,
+    output_type=_LISTRESPONSE,
+    serialized_options=None,
+  ),
 ])
 _sym_db.RegisterServiceDescriptor(_DEPENDENCYSERVICE)
 
diff --git a/service/dependency.py b/service/dependency.py
index 642ac2e..a2cdf54 100644
--- a/service/dependency.py
+++ b/service/dependency.py
@@ -9,7 +9,9 @@
 
 import os
 import re
+from typing import List, Optional
 
+from chromite.lib import build_target_lib
 from chromite.lib import constants
 from chromite.lib import cros_build_lib
 from chromite.lib import cros_logging
@@ -356,6 +358,38 @@
   return results, sdk_results
 
 
+def GetDependencies(sysroot_path: str,
+                    build_target: build_target_lib.BuildTarget,
+                    src_paths: Optional[List[str]] = None,
+                    packages: Optional[List[str]] = None) -> List[str]:
+  """Return the packages dependent on the given source paths for |board|.
+
+  Args:
+    sysroot_path: The path to the sysroot.
+    build_target: The build_target whose dependencies are being calculated.
+    src_paths: List of paths for which to get a list of dependent packages. If
+      empty / None returns all package dependencies.
+    packages: The packages that need to be built, or empty / None to use the
+      default list.
+
+  Returns:
+    The relevant package dependencies based on the given list of packages and
+      src_paths.
+  """
+  json_deps, _sdk_json_deps = GetBuildDependency(
+      sysroot_path, build_target.name, packages=packages)
+
+  relevant_packages = set()
+  for cpv, dep_src_paths in json_deps['source_path_mapping'].items():
+    if not src_paths or (set(dep_src_paths) & set(src_paths)):
+      relevant_packages.add(cpv)
+
+  relevant_package_deps = set()
+  for cpv in relevant_packages:
+    relevant_package_deps.update(json_deps['package_deps'][cpv]['deps'])
+  return relevant_packages | relevant_package_deps
+
+
 def DetermineToolchainSourcePaths():
   """Returns a list of all source paths relevant to toolchain packages.
 
diff --git a/service/dependency_unittest.py b/service/dependency_unittest.py
index b1b9f66..89fb325 100644
--- a/service/dependency_unittest.py
+++ b/service/dependency_unittest.py
@@ -9,6 +9,7 @@
 
 import os
 
+from chromite.api.gen.chromiumos import common_pb2
 from chromite.lib import constants
 from chromite.lib import cros_test_lib
 from chromite.lib import osutils
@@ -17,9 +18,63 @@
 pytestmark = cros_test_lib.pytestmark_inside_only
 
 
-class DependencyTests(cros_test_lib.TestCase):
+class DependencyTests(cros_test_lib.MockTestCase):
   """General unittests for dependency module."""
 
+  def setUp(self):
+    self.json_deps = {
+        'target_board': 'deathstar',
+        'sysroot_path': '/build/deathstar',
+        'package_deps': {
+            'commander/darthvader-1.49.3.3': {
+                'action': 'merge',
+                'category': 'commander',
+                'cpes': [],
+                'deps': ['troop/clone', 'troop/robot'],
+                'rev_deps': [],
+                'full_name': 'commander/darthvader-1.49.3.3',
+                'name': 'darthvader',
+                'version': '1.49.3.3'
+            },
+            'troop/clone-1.2.3': {
+                'action': 'merge',
+                'category': 'troop',
+                'cpes': [],
+                'deps': ['equipment/jetpack'],
+                'rev_deps': ['commander/darthvader'],
+                'full_name': 'troop/clone-1.2.3',
+                'name': 'clone',
+                'version': '1.2.3'
+            },
+            'troop/robot-2.3.4': {
+                'action': 'merge',
+                'category': 'troop',
+                'cpes': [],
+                'deps': [],
+                'rev_deps': ['commander/darthvader'],
+                'full_name': 'troop/robot-2.3.4',
+                'name': 'robot',
+                'version': '2.3.4'
+            },
+            'equipment/jetpack-3.4.5': {
+                'action': 'merge',
+                'category': 'equipment',
+                'cpes': [],
+                'deps': [],
+                'rev_deps': ['commander/darthvader'],
+                'full_name': 'equipment/jetpack-3.4.5',
+                'name': 'jetpack',
+                'version': '3.4.5'
+            },
+        },
+        'source_path_mapping': {
+            'commander/darthvader-1.49.3.3': ['/control/room'],
+            'troop/clone-1.2.3': ['/bunker'],
+            'troop/robot-2.3.4': ['/factory'],
+            'equipment/jetpack-3.4.5': ['/factory'],
+        },
+    }
+
   def testNormalizeSourcePathsCollapsingSubPaths(self):
     self.assertEqual(
         dependency.NormalizeSourcePaths(
@@ -54,3 +109,37 @@
       self.assertEqual(
           dependency.NormalizeSourcePaths([foo_dir, ab_cd_file, bar_baz_dir]),
           expected_paths)
+
+  def testGetDependenciesWithDefaultArgs(self):
+    """Test GetDependencies using the default args."""
+    self.PatchObject(
+        dependency,
+        'GetBuildDependency',
+        return_value=(self.json_deps, self.json_deps))
+    sysroot_path = '/build/deathstar'
+    build_target = common_pb2.BuildTarget(name='target')
+    actual_deps = dependency.GetDependencies(sysroot_path, build_target)
+    expected_deps = [
+        'commander/darthvader-1.49.3.3',
+        'troop/clone',
+        'troop/robot',
+        'equipment/jetpack',
+        'troop/clone-1.2.3',
+        'troop/robot-2.3.4',
+        'equipment/jetpack-3.4.5',
+    ]
+    self.assertCountEqual(expected_deps, actual_deps)
+
+  def testGetDependenciesWithSrcPaths(self):
+    """Test GetDependencies given a list of paths."""
+    self.PatchObject(
+        dependency,
+        'GetBuildDependency',
+        return_value=(self.json_deps, self.json_deps))
+    sysroot_path = '/build/deathstar'
+    build_target = common_pb2.BuildTarget(name='target')
+    src_paths = ['/bunker', '/nowhere']
+    actual_deps = dependency.GetDependencies(sysroot_path, build_target,
+                                             src_paths)
+    expected_deps = ['equipment/jetpack', 'troop/clone-1.2.3']
+    self.assertCountEqual(expected_deps, actual_deps)