summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBertrand SIMONNET <bsimonnet@chromium.org>2015-05-06 14:17:43 -0700
committerChromeOS Commit Bot <chromeos-commit-bot@chromium.org>2015-05-15 19:15:26 +0000
commite025a0e1743a40d6627d65022a3ed64ac662e2ad (patch)
tree25e0cebcb401a0aa5466971486bba71c0d52a63b
parent8bc983793b45fa19b7a22da9a0cd444d7cac8e48 (diff)
downloadchromite-e025a0e1743a40d6627d65022a3ed64ac662e2ad.tar.gz
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>
l---------bin/cros_run_unit_tests1
-rw-r--r--cbuildbot/commands.py3
-rw-r--r--lib/chroot_util.py25
-rw-r--r--lib/portage_util.py48
-rw-r--r--lib/portage_util_unittest.py35
-rw-r--r--lib/workon_helper.py8
-rw-r--r--lib/workon_helper_unittest.py16
-rw-r--r--scripts/cros_run_unit_tests.py103
8 files changed, 236 insertions, 3 deletions
diff --git a/bin/cros_run_unit_tests b/bin/cros_run_unit_tests
new file mode 120000
index 000000000..72196ceea
--- /dev/null
+++ b/bin/cros_run_unit_tests
@@ -0,0 +1 @@
+../scripts/wrapper.py \ No newline at end of file
diff --git a/cbuildbot/commands.py b/cbuildbot/commands.py
index 0db02c8d8..6f8fd69d5 100644
--- a/cbuildbot/commands.py
+++ b/cbuildbot/commands.py
@@ -604,7 +604,8 @@ def RunUnitTests(buildroot, board, full, blacklist=None, extra_env=None):
if blacklist:
cmd += ['--blacklist_packages=%s' % ' '.join(blacklist)]
- RunBuildScript(buildroot, cmd, enter_chroot=True, extra_env=extra_env or {})
+ RunBuildScript(buildroot, cmd, chromite_cmd=True, enter_chroot=True,
+ extra_env=extra_env or {})
def RunTestSuite(buildroot, board, image_dir, results_dir, test_type,
diff --git a/lib/chroot_util.py b/lib/chroot_util.py
index 3dde021af..34262aac7 100644
--- a/lib/chroot_util.py
+++ b/lib/chroot_util.py
@@ -174,3 +174,28 @@ def InitializeSysroots(blueprint):
sysroot.WriteConfig(sysroot.GenerateBrickConfig(brick_stack, bsp))
sysroot.GeneratePortageConfig()
sysroot.UpdateToolchain()
+
+
+def RunUnittests(sysroot, packages, extra_env=None):
+ """Runs the unit tests for |packages|.
+
+ Args:
+ sysroot: Path to the sysroot to build the tests in.
+ packages: List of packages to test.
+ extra_env: Python dictionary containing the extra environment variable to
+ pass to the build command.
+
+ Raises:
+ RunCommandError if the unit tests failed.
+ """
+ env = extra_env.copy() if extra_env else {}
+ env.update({
+ 'FEATURES': 'test',
+ 'PKGDIR': os.path.join(sysroot, 'test-packages'),
+ })
+
+ command = ([os.path.join(constants.CHROMITE_BIN_DIR, 'parallel_emerge'),
+ '--sysroot=%s' % sysroot, '--nodeps', '--buildpkgonly'] +
+ list(packages))
+
+ cros_build_lib.SudoRunCommand(command, extra_env=env)
diff --git a/lib/portage_util.py b/lib/portage_util.py
index d3a98c724..6273e11bd 100644
--- a/lib/portage_util.py
+++ b/lib/portage_util.py
@@ -23,6 +23,7 @@ from chromite.lib import cros_logging as logging
from chromite.lib import gerrit
from chromite.lib import git
from chromite.lib import osutils
+from chromite.lib import parallel
# The parsed output of running `ebuild <ebuild path> info`.
@@ -55,6 +56,10 @@ _blank_or_eapi_re = re.compile(r'^\s*(?:#|EAPI=|$)')
WORKON_EBUILD_VERSION = '9999'
WORKON_EBUILD_SUFFIX = '-%s.ebuild' % WORKON_EBUILD_VERSION
+UNITTEST_PACKAGE_BLACKLIST = set((
+ 'sys-devel/binutils',
+))
+
class MissingOverlayException(Exception):
"""This exception indicates that a needed overlay is missing."""
@@ -459,6 +464,7 @@ class EBuild(object):
self.is_workon = False
self.is_stable = False
self.is_blacklisted = False
+ self.has_test = False
self._ReadEBuild(path)
@staticmethod
@@ -473,6 +479,7 @@ class EBuild(object):
is_workon = False
is_stable = False
is_blacklisted = False
+ has_test = False
for line in fileinput.input(ebuild_path):
if line.startswith('inherit ') and 'cros-workon' in line:
is_workon = True
@@ -482,15 +489,19 @@ class EBuild(object):
is_stable = True
elif line.startswith('CROS_WORKON_BLACKLIST='):
is_blacklisted = True
+ elif (line.startswith('src_test()') or
+ line.startswith('platform_pkg_test()')):
+ has_test = True
fileinput.close()
- return is_workon, is_stable, is_blacklisted
+ return is_workon, is_stable, is_blacklisted, has_test
def _ReadEBuild(self, path):
"""Determine the settings of `is_workon`, `is_stable` and is_blacklisted
These are determined using the static Classify function.
"""
- self.is_workon, self.is_stable, self.is_blacklisted = EBuild.Classify(path)
+ (self.is_workon, self.is_stable,
+ self.is_blacklisted, self.has_test) = EBuild.Classify(path)
@staticmethod
def GetCrosWorkonVars(ebuild_path, pkg_name):
@@ -1597,3 +1608,36 @@ def CleanOutdatedBinaryPackages(sysroot):
"""Cleans outdated binary packages from |sysroot|."""
return cros_build_lib.RunCommand(
[cros_build_lib.GetSysrootToolPath(sysroot, 'eclean'), '-d', 'packages'])
+
+
+def _CheckHasTest(cp, sysroot):
+ """Checks if the ebuild for |cp| has tests.
+
+ Args:
+ cp: A portage package in the form category/package_name.
+ sysroot: Path to the sysroot.
+
+ Returns:
+ |cp| if the ebuild for |cp| defines a test stanza, None otherwise.
+ """
+ ebuild = EBuild(FindEbuildForPackage(cp, sysroot))
+ return cp if ebuild.has_test else None
+
+
+def PackagesWithTest(sysroot, packages):
+ """Returns the subset of |packages| that have unit tests.
+
+ Args:
+ sysroot: Path to the sysroot.
+ packages: List of packages to filter.
+
+ Returns:
+ The subset of |packages| that defines unit tests.
+ """
+ inputs = [(cp, sysroot) for cp in packages]
+ pkg_with_test = set(parallel.RunTasksInProcessPool(_CheckHasTest, inputs))
+
+ # CheckHasTest will return None for packages that do not have tests. We can
+ # discard that value.
+ pkg_with_test.discard(None)
+ return pkg_with_test
diff --git a/lib/portage_util_unittest.py b/lib/portage_util_unittest.py
index 259d61ea6..3470f5d8a 100644
--- a/lib/portage_util_unittest.py
+++ b/lib/portage_util_unittest.py
@@ -45,6 +45,26 @@ class _DummyCommandResult(object):
class EBuildTest(cros_test_lib.MoxTestCase):
"""Ebuild related tests."""
+ _MULTILINE_WITH_TEST = """
+hello
+src_test() {
+}"""
+
+ _MULTILINE_NO_TEST = """
+hello
+src_compile() {
+}"""
+
+ _MULTILINE_COMMENTED = """
+#src_test() {
+# notactive
+# }"""
+
+ _MULTILINE_PLATFORM = """
+platform_pkg_test() {
+}"""
+
+ _SINGLE_LINE_TEST = 'src_test() { echo "foo" }'
def _makeFakeEbuild(self, fake_ebuild_path, fake_ebuild_content=''):
self.mox.StubOutWithMock(fileinput, 'input')
@@ -142,6 +162,21 @@ class EBuildTest(cros_test_lib.MoxTestCase):
fake_ebuild_path, fake_ebuild_content=['CROS_WORKON_BLACKLIST="1"\n'])
self.assertEquals(fake_ebuild.is_blacklisted, True)
+ def testHasTest(self):
+ """Tests that we detect test stanzas correctly."""
+ def run_case(content, expected):
+ with osutils.TempDir() as temp:
+ ebuild = os.path.join(temp, 'overlay', 'app-misc',
+ 'foo-0.0.1-r1.ebuild')
+ osutils.WriteFile(ebuild, content, makedirs=True)
+ self.assertEqual(expected, portage_util.EBuild(ebuild).has_test)
+
+ run_case(self._MULTILINE_WITH_TEST, True)
+ run_case(self._MULTILINE_NO_TEST, False)
+ run_case(self._MULTILINE_COMMENTED, False)
+ run_case(self._MULTILINE_PLATFORM, True)
+ run_case(self._SINGLE_LINE_TEST, True)
+
class ProjectAndPathTest(cros_test_lib.MoxTempDirTestCase):
"""Project and Path related tests."""
diff --git a/lib/workon_helper.py b/lib/workon_helper.py
index 34e9f030d..176bef8e6 100644
--- a/lib/workon_helper.py
+++ b/lib/workon_helper.py
@@ -691,3 +691,11 @@ class WorkonHelper(object):
atoms = self._GetCanonicalAtoms(packages)
for atom in atoms:
self.RunCommandInAtomSourceDirectory(atom, command)
+
+ def InstalledWorkonAtoms(self):
+ """Returns the set of installed cros_workon packages."""
+ installed_cp = set()
+ for pkg in portage_util.PortageDB(self._sysroot).InstalledPackages():
+ installed_cp.add('%s/%s' % (pkg.category, pkg.package))
+
+ return set(a for a in self.ListAtoms(use_all=True) if a in installed_cp)
diff --git a/lib/workon_helper_unittest.py b/lib/workon_helper_unittest.py
index 99cf7a85c..8b3271e06 100644
--- a/lib/workon_helper_unittest.py
+++ b/lib/workon_helper_unittest.py
@@ -6,6 +6,7 @@
from __future__ import print_function
+import collections
import os
from chromite.lib import cros_test_lib
@@ -32,6 +33,10 @@ BOARD_OVERLAY_DIR = 'overlay-' + BOARD
HOST_OVERLAY_DIR = 'overlay-host'
+InstalledPackageMock = collections.namedtuple('InstalledPackage',
+ ('category', 'package'))
+
+
class WorkonHelperTest(cros_test_lib.MockTempDirTestCase):
"""Tests for chromite.lib.workon_helper."""
@@ -106,6 +111,11 @@ class WorkonHelperTest(cros_test_lib.MockTempDirTestCase):
# Patch the modules interfaces to the rest of the world.
self.PatchObject(portage_util, 'FindEbuildForPackage',
self._MockFindEbuildForPackage)
+
+ # Assume only versioned-packages is installed.
+ self.PatchObject(
+ portage_util.PortageDB, 'InstalledPackages',
+ return_value=[InstalledPackageMock('sys-apps', 'versioned-package')])
# This basically turns off behavior related to adding repositories to
# minilayouts.
self.PatchObject(git.ManifestCheckout, 'IsFullManifest', return_value=True)
@@ -305,3 +315,9 @@ class WorkonHelperTest(cros_test_lib.MockTempDirTestCase):
self.assertNotExists(file_path)
helper.RunCommandInPackages([WORKON_ONLY_ATOM], 'touch %s' % file_name)
self.assertExists(file_path)
+
+ def testInstalledWorkonAtoms(self):
+ """Test that we can list all the cros workon atoms that are installed."""
+ helper = self.CreateHelper()
+ self.assertEqual(set([VERSIONED_WORKON_ATOM]),
+ helper.InstalledWorkonAtoms())
diff --git a/scripts/cros_run_unit_tests.py b/scripts/cros_run_unit_tests.py
new file mode 100644
index 000000000..b179121b1
--- /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