Build API: Adding setup board functionality

BUG=chromium:905026, b:128844647
TEST=run_tests

Change-Id: If828d4da8778a5456aa3ab74c79cf86c55b10faa
Reviewed-on: https://chromium-review.googlesource.com/1529330
Commit-Ready: Alex Klein <saklein@chromium.org>
Tested-by: Alex Klein <saklein@chromium.org>
Reviewed-by: Evan Hernandez <evanhernandez@chromium.org>
diff --git a/api/controller/sysroot_unittest.py b/api/controller/sysroot_unittest.py
new file mode 100644
index 0000000..d61fa74
--- /dev/null
+++ b/api/controller/sysroot_unittest.py
@@ -0,0 +1,188 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019 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.
+
+"""Sysroot controller tests."""
+
+from __future__ import print_function
+
+import os
+
+from chromite.api.controller import sysroot as sysroot_controller
+from chromite.api.gen.chromite.api import sysroot_pb2
+from chromite.lib import build_target_util
+from chromite.lib import cros_build_lib
+from chromite.lib import cros_test_lib
+from chromite.lib import osutils
+from chromite.lib import portage_util
+from chromite.lib import sysroot_lib
+from chromite.service import sysroot as sysroot_service
+
+
+class CreateTest(cros_test_lib.MockTestCase):
+  """Create function tests."""
+
+  def _InputProto(self, build_target=None, profile=None, replace=False,
+                  current=False):
+    """Helper to build and input proto instance."""
+    proto = sysroot_pb2.SysrootCreateRequest()
+    if build_target:
+      proto.build_target.name = build_target
+    if profile:
+      proto.profile.name = profile
+    if replace:
+      proto.flags.replace = replace
+    if current:
+      proto.flags.chroot_current = current
+
+    return proto
+
+  def _OutputProto(self):
+    """Helper to build output proto instance."""
+    return sysroot_pb2.SysrootCreateResponse()
+
+  def testArgumentValidation(self):
+    """Test the input argument validation."""
+    # Error when no name provided.
+    in_proto = self._InputProto()
+    out_proto = self._OutputProto()
+    with self.assertRaises(cros_build_lib.DieSystemExit):
+      sysroot_controller.Create(in_proto, out_proto)
+
+    # Valid when board passed.
+    result = sysroot_lib.Sysroot('/sysroot/path')
+    patch = self.PatchObject(sysroot_service, 'Create', return_value=result)
+    in_proto = self._InputProto('board')
+    out_proto = self._OutputProto()
+    sysroot_controller.Create(in_proto, out_proto)
+    patch.assert_called_once()
+
+  def testArgumentHandling(self):
+    """Test the arguments get processed and passed correctly."""
+    sysroot_path = '/sysroot/path'
+
+    sysroot = sysroot_lib.Sysroot(sysroot_path)
+    create_patch = self.PatchObject(sysroot_service, 'Create',
+                                    return_value=sysroot)
+    target_patch = self.PatchObject(build_target_util, 'BuildTarget')
+    rc_patch = self.PatchObject(sysroot_service, 'SetupBoardRunConfig')
+
+    # Default values.
+    board = 'board'
+    profile = None
+    force = False
+    upgrade_chroot = True
+    in_proto = self._InputProto(build_target=board, profile=profile,
+                                replace=force, current=not upgrade_chroot)
+    out_proto = self._OutputProto()
+    sysroot_controller.Create(in_proto, out_proto)
+
+    # Default value checks.
+    target_patch.assert_called_with(name=board, profile=profile)
+    rc_patch.assert_called_with(force=force, upgrade_chroot=upgrade_chroot)
+    self.assertEqual(board, out_proto.sysroot.build_target.name)
+    self.assertEqual(sysroot_path, out_proto.sysroot.path)
+
+    # Not default values.
+    create_patch.reset_mock()
+    board = 'board'
+    profile = 'profile'
+    force = True
+    upgrade_chroot = False
+    in_proto = self._InputProto(build_target=board, profile=profile,
+                                replace=force, current=not upgrade_chroot)
+    out_proto = self._OutputProto()
+    sysroot_controller.Create(in_proto, out_proto)
+
+    # Not default value checks.
+    target_patch.assert_called_with(name=board, profile=profile)
+    rc_patch.assert_called_with(force=force, upgrade_chroot=upgrade_chroot)
+    self.assertEqual(board, out_proto.sysroot.build_target.name)
+    self.assertEqual(sysroot_path, out_proto.sysroot.path)
+
+
+class InstallToolchainTest(cros_test_lib.MockTempDirTestCase):
+  """Install toolchain function tests."""
+
+  def setUp(self):
+    self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
+    self.board = 'board'
+    self.sysroot = os.path.join(self.tempdir, 'board')
+    self.invalid_sysroot = os.path.join(self.tempdir, 'invalid', 'sysroot')
+    osutils.SafeMakedirs(self.sysroot)
+
+  def _InputProto(self, build_target=None, sysroot_path=None,
+                  compile_source=False):
+    """Helper to build and input proto instance."""
+    proto = sysroot_pb2.InstallToolchainRequest()
+    if build_target:
+      proto.sysroot.build_target.name = build_target
+    if sysroot_path:
+      proto.sysroot.path = sysroot_path
+    if compile_source:
+      proto.flags.compile_source = compile_source
+
+    return proto
+
+  def _OutputProto(self):
+    """Helper to build output proto instance."""
+    return sysroot_pb2.InstallToolchainResponse()
+
+  def testArgumentValidation(self):
+    """Test the argument validation."""
+    # Test errors on missing inputs.
+    out_proto = self._OutputProto()
+    # Both missing.
+    in_proto = self._InputProto()
+    with self.assertRaises(cros_build_lib.DieSystemExit):
+      sysroot_controller.InstallToolchain(in_proto, out_proto)
+
+    # Sysroot path missing.
+    in_proto = self._InputProto(build_target=self.board)
+    with self.assertRaises(cros_build_lib.DieSystemExit):
+      sysroot_controller.InstallToolchain(in_proto, out_proto)
+
+    # Build target name missing.
+    in_proto = self._InputProto(sysroot_path=self.sysroot)
+    with self.assertRaises(cros_build_lib.DieSystemExit):
+      sysroot_controller.InstallToolchain(in_proto, out_proto)
+
+    # Both provided, but invalid sysroot path.
+    in_proto = self._InputProto(build_target=self.board,
+                                sysroot_path=self.invalid_sysroot)
+    with self.assertRaises(cros_build_lib.DieSystemExit):
+      sysroot_controller.InstallToolchain(in_proto, out_proto)
+
+  def testSuccessOutputHandling(self):
+    """Test the output is processed and recorded correctly."""
+    self.PatchObject(sysroot_service, 'InstallToolchain')
+    out_proto = self._OutputProto()
+    in_proto = self._InputProto(build_target=self.board,
+                                sysroot_path=self.sysroot)
+
+    rc = sysroot_controller.InstallToolchain(in_proto, out_proto)
+    self.assertFalse(rc)
+    self.assertFalse(out_proto.failed_packages)
+
+
+  def testErrorOutputHandling(self):
+    """Test the error output is processed and recorded correctly."""
+    out_proto = self._OutputProto()
+    in_proto = self._InputProto(build_target=self.board,
+                                sysroot_path=self.sysroot)
+
+    err_pkgs = ['cat/pkg', 'cat2/pkg2']
+    err_cpvs = [portage_util.SplitCPV(pkg, strict=False) for pkg in err_pkgs]
+    expected = [('cat', 'pkg'), ('cat2', 'pkg2')]
+    err = sysroot_lib.ToolchainInstallError('Error',
+                                            cros_build_lib.CommandResult(),
+                                            tc_info=err_cpvs)
+    self.PatchObject(sysroot_service, 'InstallToolchain', side_effect=err)
+
+    rc = sysroot_controller.InstallToolchain(in_proto, out_proto)
+    self.assertTrue(rc)
+    self.assertTrue(out_proto.failed_packages)
+    for package in out_proto.failed_packages:
+      cat_pkg = (package.category, package.package_name)
+      self.assertIn(cat_pkg, expected)