cros_run_unit_tests: Convert to python.

This replaces the old bash script installed by crostestutils.

BUG=chromium:486263
TEST=unittests.
TEST=trybot run on gizmo.
TEST=`cros_run_unit_tests --pretend --board=gizmo` test the same
  packages with the old and new script.

Change-Id: Idfa3501c8639b41d97165ff80bb06522ed734c3a
Reviewed-on: https://chromium-review.googlesource.com/270148
Trybot-Ready: Bertrand Simonnet <bsimonnet@chromium.org>
Tested-by: Bertrand Simonnet <bsimonnet@chromium.org>
Reviewed-by: Bertrand Simonnet <bsimonnet@chromium.org>
Commit-Queue: Bertrand Simonnet <bsimonnet@chromium.org>
diff --git a/scripts/cros_run_unit_tests.py b/scripts/cros_run_unit_tests.py
new file mode 100644
index 0000000..b179121
--- /dev/null
+++ b/scripts/cros_run_unit_tests.py
@@ -0,0 +1,103 @@
+# Copyright 2015 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.
+
+"""Tool to run ebuild unittests."""
+
+from __future__ import print_function
+
+import os
+
+from chromite.lib import commandline
+from chromite.lib import chroot_util
+from chromite.lib import cros_build_lib
+from chromite.lib import cros_logging as logging
+from chromite.lib import osutils
+from chromite.lib import workon_helper
+from chromite.lib import portage_util
+
+
+def ParseArgs(argv):
+  """Parse arguments.
+
+  Args:
+    argv: array of arguments passed to the script.
+  """
+  parser = commandline.ArgumentParser(description=__doc__)
+
+  target = parser.add_mutually_exclusive_group(required=True)
+  target.add_argument('--sysroot', type='path', help='Path to the sysroot.')
+  target.add_argument('--board', help='Board name.')
+
+  parser.add_argument('--pretend', default=False, action='store_true',
+                      help='Show the list of packages to be tested and return.')
+  parser.add_argument('--noinstalled_only', dest='installed', default=True,
+                      action='store_false',
+                      help='Test all testable packages, even if they are not '
+                      'currently installed.')
+  parser.add_argument('--package_file', type='path',
+                      help='Path to a file containing the list of packages '
+                      'that should be tested.')
+  parser.add_argument('--blacklist_packages', dest='package_blacklist',
+                      help='Space-separated list of blacklisted packages.')
+  parser.add_argument('--packages',
+                      help='Space-separated list of packages to test.')
+  parser.add_argument('--nowithdebug', action='store_true',
+                      help="Don't build the tests with USE=cros-debug")
+
+  options = parser.parse_args(argv)
+  options.Freeze()
+  return options
+
+
+def main(argv):
+  opts = ParseArgs(argv)
+
+  cros_build_lib.AssertInsideChroot()
+
+  sysroot = opts.sysroot or cros_build_lib.GetSysroot(opts.board)
+  package_blacklist = portage_util.UNITTEST_PACKAGE_BLACKLIST
+  if opts.package_blacklist:
+    package_blacklist |= set(opts.package_blacklist.split())
+
+  packages = set()
+  # The list of packages to test can be passed as a file containing a
+  # space-separated list of package names.
+  # This is used by the builder to test only the packages that were upreved.
+  if opts.package_file and os.path.exists(opts.package_file):
+    packages = set(osutils.ReadFile(opts.package_file).split())
+
+  if opts.packages:
+    packages |= set(opts.packages.split())
+
+  # If no packages were specified, use all testable packages.
+  if not (opts.packages or opts.package_file):
+    workon = workon_helper.WorkonHelper(sysroot)
+    packages = (workon.InstalledWorkonAtoms() if opts.installed
+                else workon.ListAtoms(use_all=True))
+
+  for cp in packages & package_blacklist:
+    logging.info('Skipping blacklisted package %s.', cp)
+
+  packages = packages - package_blacklist
+  pkg_with_test = portage_util.PackagesWithTest(sysroot, packages)
+
+  if packages - pkg_with_test:
+    logging.warning('The following packages do not have tests:')
+    logging.warning('\n'.join(sorted(packages - pkg_with_test)))
+
+  if opts.pretend:
+    print('\n'.join(sorted(pkg_with_test)))
+    return
+
+  env = None
+  if opts.nowithdebug:
+    use_flags = os.environ.get('USE', '')
+    use_flags += ' -cros-debug'
+    env = {'USE': use_flags}
+
+  try:
+    chroot_util.RunUnittests(sysroot, pkg_with_test, extra_env=env)
+  except cros_build_lib.RunCommandError:
+    logging.error('Unittests failed.')
+    raise