arc: add anroid-installer
Design Doc: go/ptdnbss-new-design
These script will ba used by push_to_device, board_specific_setup and
chromeos-base/android-{container,vm}-* ebuilds.
BUG=b:147893231, b:123380475
TEST=python3 ./android_installer_unittest.py
TEST=cros lint --py3 *.py
Cq-Depend: chrome-internal:3174029
Change-Id: I99ba6366c37fb1aee756fcb86ab9b489cad10a01
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2306850
Tested-by: Boleyn Su <boleynsu@chromium.org>
Reviewed-by: Hidehiko Abe <hidehiko@chromium.org>
Commit-Queue: Boleyn Su <boleynsu@chromium.org>
Auto-Submit: Boleyn Su <boleynsu@chromium.org>
diff --git a/arc/android-installer/android_installer.py b/arc/android-installer/android_installer.py
new file mode 100755
index 0000000..1e3584a
--- /dev/null
+++ b/arc/android-installer/android_installer.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+# Copyright 2020 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.
+
+"""Android Installer
+
+This script will be called by push_to_device, chromeos-base/android-{container,
+vm}-* ebuilds and board_specific_setup.
+"""
+
+from __future__ import print_function
+
+import argparse
+import enum
+import sys
+from typing import Dict, List
+
+
+class AndroidInstallerCaller(enum.Enum):
+ """Android Installer Caller"""
+ PUSH_TO_DEVICE = 0
+ EBUILD_SRC_COMPILE = 1
+ EBUILD_SRC_INSTALL = 2
+ EBUILD_SRC_TEST = 3
+ BOARD_SPECIFIC_SETUP = 4
+ BOARD_SPECIFIC_SETUP_TEST = 5
+
+ @staticmethod
+ def allowed_callers() -> List[str]:
+ return [name.lower() for name in
+ AndroidInstallerCaller.__members__.keys()]
+
+ @staticmethod
+ # TODO(boleynsu): annotate the return type once we are using python>3.6
+ def from_str(caller: str):
+ return AndroidInstallerCaller[caller.upper()]
+
+
+
+class AndroidInstaller:
+ """Android Installer"""
+
+ # env variables used by Android Installer
+ env: Dict[str, str]
+ # use flags used by Android Installer
+ use: Dict[str, bool]
+ # caller of Android Installer
+ caller: AndroidInstallerCaller
+
+ def __init__(self, argv: List[str]) -> None:
+ """Parse the arguments."""
+
+ parser = argparse.ArgumentParser(
+ usage=
+ """
+ Example:
+ %(prog)s --env=ROOT=/build/rammus-arc-r --use=-cheets_local_img \\
+ --caller push_to_device
+
+ %(prog)s --env ROOT=/build/rammus-arc-r --env PV=9999 \\
+ --env KEY_WITH_NO_VALUE= --use +cheets_local_img \\
+ --use +chromeos-base/chromeos-cheets:android-container-pi \\
+ --caller ebuild_src_compile
+ Note:
+ The dash used in --use is special so we must use --use=-use_flag
+ instead of --use -use_flag
+ """
+ )
+ parser.add_argument('--env', action='append',
+ help='the format is KEY=VALUE')
+ parser.add_argument('--use', action='append',
+ help='the format is +use_flag or -use_flag')
+ parser.add_argument(
+ '--caller', choices=AndroidInstallerCaller.allowed_callers(),
+ help='the caller of this script')
+
+ args = parser.parse_args(args=argv)
+
+ self.env = dict()
+ if args.env:
+ for e in args.env:
+ kv = e.split('=', 1)
+ if len(kv) < 2:
+ raise ValueError(
+ 'Invalid --env %s argument.' % e +
+ ' = is missing. For a key with no value, please use --env KEY=')
+ key, value = kv
+ if not key:
+ raise ValueError(
+ 'Invalid --env %s argument.' % e +
+ ' The key should not be empty.')
+ # The later argument will overwrite the former one.
+ self.env[key] = value
+
+ self.use = dict()
+ if args.use:
+ for u in args.use:
+ key = u[1:]
+ if u[0] == '+':
+ value = True
+ elif u[0] == '-':
+ value = False
+ else:
+ raise ValueError(
+ 'Invalid --use %s argument.' % u +
+ ' The first character should be + or -.')
+ # The later argument will overwrite the former one.
+ self.use[key] = value
+
+ if not args.caller:
+ raise ValueError('--caller must be specified')
+ self.caller = AndroidInstallerCaller.from_str(args.caller)
+
+ def main(self) -> None:
+ if self.caller in [AndroidInstallerCaller.PUSH_TO_DEVICE,
+ AndroidInstallerCaller.EBUILD_SRC_COMPILE]:
+ self.ebuild_src_compile()
+ if self.caller in [AndroidInstallerCaller.PUSH_TO_DEVICE,
+ AndroidInstallerCaller.EBUILD_SRC_TEST]:
+ self.ebuild_src_test()
+ if self.caller in [AndroidInstallerCaller.PUSH_TO_DEVICE,
+ AndroidInstallerCaller.EBUILD_SRC_INSTALL]:
+ self.ebuild_src_install()
+ if self.caller in [AndroidInstallerCaller.PUSH_TO_DEVICE,
+ AndroidInstallerCaller.BOARD_SPECIFIC_SETUP]:
+ self.board_specific_setup()
+ if self.caller in [AndroidInstallerCaller.PUSH_TO_DEVICE,
+ AndroidInstallerCaller.BOARD_SPECIFIC_SETUP_TEST]:
+ self.board_specific_setup_test()
+
+ def ebuild_src_compile(self) -> None:
+ # TODO(boleynsu): implement this
+ raise NotImplementedError()
+
+ def ebuild_src_test(self) -> None:
+ # TODO(boleynsu): implement this
+ raise NotImplementedError()
+
+ def ebuild_src_install(self) -> None:
+ # TODO(boleynsu): implement this
+ raise NotImplementedError()
+
+ def board_specific_setup(self) -> None:
+ # TODO(boleynsu): implement this
+ raise NotImplementedError()
+
+ def board_specific_setup_test(self) -> None:
+ # TODO(boleynsu): implement this
+ raise NotImplementedError()
+
+
+if __name__ == '__main__':
+ AndroidInstaller(sys.argv[1:]).main()
diff --git a/arc/android-installer/android_installer_unittest.py b/arc/android-installer/android_installer_unittest.py
new file mode 100644
index 0000000..29df107
--- /dev/null
+++ b/arc/android-installer/android_installer_unittest.py
@@ -0,0 +1,101 @@
+# Copyright 2020 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.
+
+"""Unit tests for android_installer.py"""
+
+import unittest
+import unittest.mock
+
+import android_installer
+
+
+class AndroidInstallerCallerTest(unittest.TestCase):
+ """Unit tests for AndroidInstallerCaller"""
+
+ def test_from_str(self):
+ self.assertRaises(KeyError,
+ lambda: android_installer.AndroidInstallerCaller.from_str(
+ 'invalid_caller'))
+
+
+class AndroidInstallerTest(unittest.TestCase):
+ """Unit tests for AndroidInstaller"""
+
+ def test_env(self):
+ # Test --env with '=' in its value
+ self.assertEqual(android_installer.AndroidInstaller(
+ ['--env', 'a=b=c', '--caller', 'ebuild_src_compile']).env, {'a': 'b=c'})
+ # Test --env with no value
+ self.assertEqual(android_installer.AndroidInstaller(
+ ['--env', 'a=', '--caller', 'ebuild_src_compile']).env, {'a': ''})
+ # Test --env with no =
+ self.assertRaises(ValueError, lambda: android_installer.AndroidInstaller(
+ ['--env', 'a', '--caller', 'ebuild_src_compile']))
+ # Test --env with empty key
+ self.assertRaises(ValueError, lambda: android_installer.AndroidInstaller(
+ ['--env', '=a', '--caller', 'ebuild_src_compile']))
+ # Test that the later argument will overwrite the former one.
+ self.assertEqual(android_installer.AndroidInstaller(
+ ['--env', 'a=1', '--env', 'a=2', '--caller', 'ebuild_src_compile']).env,
+ {'a': '2'})
+ # Test when no --env is given.
+ self.assertEqual(android_installer.AndroidInstaller(
+ ['--caller', 'ebuild_src_compile']).env, {})
+
+
+ def test_use(self):
+ # Test enabling use flag
+ self.assertEqual(android_installer.AndroidInstaller(
+ ['--use', '+x', '--caller', 'ebuild_src_compile']).use, {'x': True})
+ # Test disabling use flag
+ self.assertEqual(android_installer.AndroidInstaller(
+ ['--use=-x', '--caller', 'ebuild_src_compile']).use, {'x': False})
+ # Test invalid use flag
+ self.assertRaises(ValueError, lambda: android_installer.AndroidInstaller(
+ ['--use', '+x', '--use=*x', '--caller', 'ebuild_src_compile']))
+ # Test that the later argument will overwrite the former one.
+ self.assertEqual(android_installer.AndroidInstaller(
+ ['--use', '+x', '--use=-x', '--caller', 'ebuild_src_compile']).use,
+ {'x': False})
+ # Test when no use flags are given.
+ self.assertEqual(android_installer.AndroidInstaller(
+ ['--caller', 'ebuild_src_compile']).use, {})
+
+ def test_caller(self):
+ # Test --caller ebuild_src_compile
+ self.assertEqual(
+ android_installer.AndroidInstaller(
+ ['--caller', 'ebuild_src_compile']).caller,
+ android_installer.AndroidInstallerCaller.EBUILD_SRC_COMPILE)
+ # Test invalid caller
+ self.assertRaises(SystemExit, lambda: android_installer.AndroidInstaller(
+ ['--caller', 'invalid_caller']))
+ # Test when caller is not specified
+ self.assertRaises(ValueError, lambda: android_installer.AndroidInstaller(
+ []))
+
+ def test_main(self):
+ all_fn = ['ebuild_src_compile', 'ebuild_src_install', 'ebuild_src_test',
+ 'board_specific_setup', 'board_specific_setup_test']
+
+ def test_called(caller, called_fn):
+ mock = unittest.mock.Mock(android_installer.AndroidInstaller)
+ mock.caller = android_installer.AndroidInstallerCaller.from_str(caller)
+ android_installer.AndroidInstaller.main(mock)
+ for fn in all_fn:
+ if fn in called_fn:
+ mock.__getattr__(fn).assert_called_once()
+ else:
+ mock.__getattr__(fn).assert_not_called()
+
+ for caller in android_installer.AndroidInstallerCaller.allowed_callers():
+ if caller != 'push_to_device':
+ self.assertIn(caller, all_fn)
+ test_called(caller, [caller])
+
+ test_called('push_to_device', all_fn)
+
+
+if __name__ == '__main__':
+ unittest.main()