blob: 83ef51a1db3bf133b9eb5cdc0ba5bc4e453c81f8 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2011 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 cros_portage_upgrade.py."""
import cStringIO
import exceptions
import optparse
import os
import re
import sys
import unittest
import mox
import cros_portage_upgrade as cpu
import parallel_emerge
import portage.tests.resolver.ResolverPlayground as respgnd
# Configuration for generating a temporary valid ebuild hierarchy.
# TODO(mtennant): Wrap this mechanism to create multiple overlays.
EBUILDS = {
"dev-libs/A-1": {"RDEPEND" : "dev-libs/B"},
"dev-libs/A-2": {"RDEPEND" : "dev-libs/B"},
"dev-libs/B-1": {"RDEPEND" : "dev-libs/C"},
"dev-libs/B-2": {"RDEPEND" : "dev-libs/C"},
"dev-libs/C-1": {},
"dev-libs/C-2": {},
"dev-libs/D-1": {},
"dev-libs/D-2": {},
"dev-libs/E-2": {},
"dev-libs/E-3": {},
"chromeos-base/flimflam-0.0.1-r228": {
"EAPI" : "2",
"SLOT" : "0",
"KEYWORDS" : "amd64 x86 arm",
"RDEPEND" : ">=dev-libs/D-2",
},
"chromeos-base/libchrome-57098-r4": {
"EAPI" : "2",
"SLOT" : "0",
"KEYWORDS" : "amd64 x86 arm",
"RDEPEND" : ">=dev-libs/E-2",
},
"chromeos-base/libcros-1": {
"EAPI" : "2",
"SLOT" : "0",
"KEYWORDS" : "amd64 x86 arm",
"RDEPEND" : "dev-libs/B dev-libs/C chromeos-base/flimflam",
"DEPEND" :
"dev-libs/B dev-libs/C chromeos-base/flimflam chromeos-base/libchrome",
},
"Virtual/libusb-0" : {
"EAPI" :"2", "SLOT" : "0",
"RDEPEND" :
"|| ( >=dev-libs/libusb-0.1.12-r1:0 dev-libs/libusb-compat " +
">=sys-freebsd/freebsd-lib-8.0[usb] )"},
"virtual/libusb-1" : {
"EAPI" :"2", "SLOT" : "1",
"RDEPEND" : ">=dev-libs/libusb-1.0.4:1"},
"dev-libs/libusb-0.1.13" : {},
"dev-libs/libusb-1.0.5" : {"SLOT":"1"},
"dev-libs/libusb-compat-1" : {},
"sys-freebsd/freebsd-lib-8": {"IUSE" : "+usb"},
"sys-fs/udev-164" : {"EAPI" : "1", "RDEPEND" : "virtual/libusb:0"},
"virtual/jre-1.5.0" : {
"SLOT" : "1.5",
"RDEPEND" : "|| ( =dev-java/sun-jre-bin-1.5.0* =virtual/jdk-1.5.0* )"},
"virtual/jre-1.5.0-r1" : {
"SLOT" : "1.5",
"RDEPEND" : "|| ( =dev-java/sun-jre-bin-1.5.0* =virtual/jdk-1.5.0* )"},
"virtual/jre-1.6.0" : {
"SLOT" : "1.6",
"RDEPEND" : "|| ( =dev-java/sun-jre-bin-1.6.0* =virtual/jdk-1.6.0* )"},
"virtual/jre-1.6.0-r1" : {
"SLOT" : "1.6",
"RDEPEND" : "|| ( =dev-java/sun-jre-bin-1.6.0* =virtual/jdk-1.6.0* )"},
"virtual/jdk-1.5.0" : {
"SLOT" : "1.5",
"RDEPEND" : "|| ( =dev-java/sun-jdk-1.5.0* dev-java/gcj-jdk )"},
"virtual/jdk-1.5.0-r1" : {
"SLOT" : "1.5",
"RDEPEND" : "|| ( =dev-java/sun-jdk-1.5.0* dev-java/gcj-jdk )"},
"virtual/jdk-1.6.0" : {
"SLOT" : "1.6",
"RDEPEND" : "|| ( =dev-java/icedtea-6* =dev-java/sun-jdk-1.6.0* )"},
"virtual/jdk-1.6.0-r1" : {
"SLOT" : "1.6",
"RDEPEND" : "|| ( =dev-java/icedtea-6* =dev-java/sun-jdk-1.6.0* )"},
"dev-java/gcj-jdk-4.5" : {},
"dev-java/gcj-jdk-4.5-r1" : {},
"dev-java/icedtea-6.1" : {},
"dev-java/icedtea-6.1-r1" : {},
"dev-java/sun-jdk-1.5" : {"SLOT" : "1.5"},
"dev-java/sun-jdk-1.6" : {"SLOT" : "1.6"},
"dev-java/sun-jre-bin-1.5" : {"SLOT" : "1.5"},
"dev-java/sun-jre-bin-1.6" : {"SLOT" : "1.6"},
"dev-java/ant-core-1.8" : {"DEPEND" : ">=virtual/jdk-1.4"},
"dev-db/hsqldb-1.8" : {"RDEPEND" : ">=virtual/jre-1.6"},
}
INSTALLED = {
"dev-libs/A-1": {"RDEPEND" : "dev-libs/B"},
"dev-libs/B-1": {"RDEPEND" : "dev-libs/C"},
"dev-libs/C-1": {},
"dev-libs/D-1": {},
"virtual/jre-1.5.0" : {
"SLOT" : "1.5",
"RDEPEND" : "|| ( =virtual/jdk-1.5.0* =dev-java/sun-jre-bin-1.5.0* )"},
"virtual/jre-1.6.0" : {
"SLOT" : "1.6",
"RDEPEND" : "|| ( =virtual/jdk-1.6.0* =dev-java/sun-jre-bin-1.6.0* )"},
"virtual/jdk-1.5.0" : {
"SLOT" : "1.5",
"RDEPEND" : "|| ( =dev-java/sun-jdk-1.5.0* dev-java/gcj-jdk )"},
"virtual/jdk-1.6.0" : {
"SLOT" : "1.6",
"RDEPEND" : "|| ( =dev-java/icedtea-6* =dev-java/sun-jdk-1.6.0* )"},
"dev-java/gcj-jdk-4.5" : {},
"dev-java/icedtea-6.1" : {},
"virtual/libusb-0" : {
"EAPI" :"2", "SLOT" : "0",
"RDEPEND" :
"|| ( >=dev-libs/libusb-0.1.12-r1:0 dev-libs/libusb-compat " +
">=sys-freebsd/freebsd-lib-8.0[usb] )"},
}
# For verifying dependency graph results
GOLDEN_DEP_GRAPHS = {
"dev-libs/A-2" : { "needs" : { "dev-libs/B-2" : "runtime" },
"action" : "merge" },
"dev-libs/B-2" : { "needs" : { "dev-libs/C-2" : "runtime" } },
"dev-libs/C-2" : { "needs" : { } },
"dev-libs/D-2" : { "needs" : { } },
"dev-libs/E-3" : { "needs" : { } },
"chromeos-base/libcros-1" : { "needs" : {
"dev-libs/B-2" : "buildtime",
"dev-libs/C-2" : "buildtime",
"chromeos-base/libchrome-57098-r4" : "buildtime",
"chromeos-base/flimflam-0.0.1-r228" : "buildtime"
} },
"chromeos-base/flimflam-0.0.1-r228" : { "needs" : {
"dev-libs/D-2" : "runtime"
} },
"chromeos-base/libchrome-57098-r4" : { "needs" : {
"dev-libs/E-3" : "runtime"
} },
}
# For verifying dependency list results
GOLDEN_DEP_LISTS = {
"dev-libs/A" : ['dev-libs/A-2', 'dev-libs/B-2', 'dev-libs/C-2'],
"dev-libs/B" : ['dev-libs/B-2', 'dev-libs/C-2'],
"dev-libs/C" : ['dev-libs/C-2'],
"virtual/libusb" : ['virtual/libusb-1', 'dev-libs/libusb-1.0.5'],
"chromeos-base/libcros" : ['chromeos-base/libcros-1',
'dev-libs/B-2',
'chromeos-base/flimflam-0.0.1-r228',
'dev-libs/D-2',
'dev-libs/C-2',
'chromeos-base/libchrome-57098-r4',
'dev-libs/E-3',
],
}
def _GetGoldenDepsList(pkg):
"""Retrieve the golden dependency list for |pkg| from GOLDEN_DEP_LISTS."""
return GOLDEN_DEP_LISTS.get(pkg, None)
def _VerifyDepsGraph(deps_graph, pkg):
"""Verfication function for Mox to validate deps graph for |pkg|."""
if deps_graph is None:
print "Error: no dependency graph passed into _GetPreOrderDepGraph"
return False
if type(deps_graph) != dict:
print "Error: dependency graph is expected to be a dict. Instead: "
print repr(deps_graph)
return False
validated = True
# Verify size
golden_deps_list = _GetGoldenDepsList(pkg)
if golden_deps_list == None:
print("Error: golden dependency list not configured for %s package" %
(pkg))
validated = False
elif len(deps_graph) != len(golden_deps_list):
print("Error: expected %d dependencies for %s package, not %d" %
(len(golden_deps_list), pkg, len(deps_graph)))
validated = False
# Verify dependencies, by comparing them to GOLDEN_DEP_GRAPHS
for p in deps_graph:
golden_pkg_info = None
try:
golden_pkg_info = GOLDEN_DEP_GRAPHS[p]
except KeyError:
print("Error: golden dependency graph not configured for %s package" %
(p))
validated = False
continue
pkg_info = deps_graph[p]
for key in golden_pkg_info:
golden_value = golden_pkg_info[key]
value = pkg_info[key]
if not value == golden_value:
print("Error: while verifying '%s' value for %s package,"
" expected:\n%r\nBut instead found:\n%r"
% (key, p, golden_value, value))
validated = False
if not validated:
print("Error: dependency graph for %s is not as expected. Instead:\n%r" %
(pkg, deps_graph))
return validated
def _GenDepsGraphVerifier(pkg):
"""Generate a graph verification function for the given package."""
return lambda deps_graph: _VerifyDepsGraph(deps_graph, pkg)
####################
### UpgraderTest ###
####################
class UpgraderTest(mox.MoxTestBase):
"""Test the Upgrader class from cros_portage_upgrade."""
def setUp(self):
mox.MoxTestBase.setUp(self)
def _MockUpgrader(self, board='test_board', package='test_package',
verbose=False, rdeps=None, srcroot=None,
stable_repo=None, upstream_repo=None, csv_file=None):
"""Set up a mocked Upgrader object with the given args."""
upgrader = self.mox.CreateMock(cpu.Upgrader)
(options, args) = self._MockUpgraderOptions(board=board, package=package,
verbose=verbose, rdeps=rdeps)
upgrader._options = options
upgrader._args = args
upgrader._stable_repo = stable_repo
upgrader._upstream_repo = upstream_repo
upgrader._csv_file = csv_file
return upgrader
def _MockUpgraderOptions(self, board='test_board', package='test_package',
srcroot=None, upstream=None,
verbose=False, rdeps=None):
"""Mock optparse.Values for use with Upgrader, and create args list.
Returns tuple with (options, args)."""
if not srcroot:
srcroot = '%s/trunk/src' % os.environ['HOME']
options = self.mox.CreateMock(optparse.Values)
options.board = board
options.verbose = verbose
options.rdeps = rdeps
options.srcroot = srcroot
options.upstream = upstream
args = [package]
return (options, args)
def _SetUpEmerge(self):
"""Prepare the temporary ebuild playground and emerge variables.
This leverages test code in existing Portage modules to create an ebuild
hierarchy. This can be a little slow."""
# TODO(mtennant): Support multiple overlays? This essentially
# creates just a default overlay.
self._playground = respgnd.ResolverPlayground(ebuilds=EBUILDS,
installed=INSTALLED)
# Set all envvars needed by emerge, since --board is being skipped.
eroot = self._playground.eroot
if eroot[-1:] == '/':
eroot = eroot[:-1]
os.environ["PORTAGE_CONFIGROOT"] = eroot
os.environ["PORTAGE_SYSROOT"] = eroot
os.environ["SYSROOT"] = eroot
os.environ.setdefault("CHROMEOS_ROOT", "%s/trunk" % os.environ["HOME"])
os.environ["PORTDIR"] = "%s/usr/portage" % eroot
def _TearDownEmerge(self):
"""Delete the temporary ebuild playground files."""
try:
self._playground.cleanup()
except AttributeError:
pass
def _GetParallelEmergeArgv(self, mocked_upgrader):
return cpu.Upgrader._GenParallelEmergeArgv(mocked_upgrader)
#
# _GetCurrentVersions testing
#
def _TestGetCurrentVersions(self, pkg):
"""Test the behavior of the Upgrader._GetCurrentVersions method.
This basically confirms that it uses the parallel_emerge module to
assemble the expected dependency graph."""
mocked_upgrader = self._MockUpgrader(board=None, package=pkg, verbose=False)
pm_argv = self._GetParallelEmergeArgv(mocked_upgrader)
self._SetUpEmerge()
# Add test-specific mocks/stubs.
# TODO(mtennant): Stubbing this method may be overkill for this unit test
self.mox.StubOutWithMock(cpu.Upgrader, '_GetPreOrderDepGraph')
# Replay script
verifier = _GenDepsGraphVerifier(pkg)
mocked_upgrader._GenParallelEmergeArgv().AndReturn(pm_argv)
cpu.Upgrader._GetPreOrderDepGraph(mox.Func(verifier)).AndReturn(['ignore'])
self.mox.ReplayAll()
# Verify
graph = cpu.Upgrader._GetCurrentVersions(mocked_upgrader)
self.mox.VerifyAll()
self._TearDownEmerge()
def testGetCurrentVersionsDevLibsA(self):
return self._TestGetCurrentVersions('dev-libs/A')
def testGetCurrentVersionsDevLibsB(self):
return self._TestGetCurrentVersions('dev-libs/B')
def testGetCurrentVersionsCrosbaseLibcros(self):
return self._TestGetCurrentVersions('chromeos-base/libcros')
#
# _GetPreOrderDepGraph testing
#
def _TestGetPreOrderDepGraph(self, pkg):
"""Test the behavior of the Upgrader._GetPreOrderDepGraph method."""
mocked_upgrader = self._MockUpgrader(board=None, package=pkg, verbose=False)
pm_argv = self._GetParallelEmergeArgv(mocked_upgrader)
self._SetUpEmerge()
# Replay script
self.mox.ReplayAll()
# Verify
deps = parallel_emerge.DepGraphGenerator()
deps.Initialize(pm_argv)
deps_tree, deps_info = deps.GenDependencyTree()
deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
deps_list = cpu.Upgrader._GetPreOrderDepGraph(deps_graph)
golden_deps_list = _GetGoldenDepsList(pkg)
self.assertEquals(deps_list, golden_deps_list)
self.mox.VerifyAll()
self._TearDownEmerge()
def testGetPreOrderDepGraphDevLibsA(self):
return self._TestGetPreOrderDepGraph('dev-libs/A')
def testGetPreOrderDepGraphDevLibsC(self):
return self._TestGetPreOrderDepGraph('dev-libs/C')
def testGetPreOrderDepGraphVirtualLibusb(self):
return self._TestGetPreOrderDepGraph('virtual/libusb')
def testGetPreOrderDepGraphCrosbaseLibcros(self):
return self._TestGetPreOrderDepGraph('chromeos-base/libcros')
#
# _SplitEBuildPath testing
#
def _TestSplitEBuildPath(self, ebuild_path, golden_result):
"""Test the behavior of the Upgrader._SplitEBuildPath method."""
mocked_upgrader = self._MockUpgrader()
# Replay script
self.mox.ReplayAll()
# Verify
result = cpu.Upgrader._SplitEBuildPath(mocked_upgrader,
ebuild_path)
self.assertEquals(result, golden_result)
self.mox.VerifyAll()
def testSplitEBuildPath1(self):
return self._TestSplitEBuildPath('/foo/bar/portage/dev-libs/A/A-2.ebuild',
('portage', 'dev-libs', 'A', 'A-2'))
def testSplitEBuildPath2(self):
return self._TestSplitEBuildPath('/foo/ooo/ccc/ppp/ppp-1.2.3-r123.ebuild',
('ooo', 'ccc', 'ppp', 'ppp-1.2.3-r123'))
#
# _GetInfoListWithOverlays testing
#
def _TestGetInfoListWithOverlays(self, pkg):
"""Test the behavior of the Upgrader._GetInfoListWithOverlays method."""
self._SetUpEmerge()
# Add test-specific mocks/stubs
# Replay script, if any
self.mox.ReplayAll()
# Verify
cpvlist = _GetGoldenDepsList(pkg)
(options, args) = self._MockUpgraderOptions(board=None,
package=pkg,
verbose=False)
upgrader = cpu.Upgrader(options, args)
cpvinfolist = upgrader._GetInfoListWithOverlays(cpvlist)
self.mox.VerifyAll()
# Verify the overlay that was found for each cpv. Always "portage" for now,
# because that is what is created by the temporary ebuild creator.
# TODO(mtennant): Support multiple overlays somehow.
for cpvinfo in cpvinfolist:
self.assertEquals('portage', cpvinfo['overlay'])
self._TearDownEmerge()
def testGetInfoListWithOverlaysDevLibsA(self):
return self._TestGetInfoListWithOverlays('dev-libs/A')
def testGetInfoListWithOverlaysCrosbaseLibcros(self):
return self._TestGetInfoListWithOverlays('chromeos-base/libcros')
#
# _UpgradePackages testing
#
# TODO(mtennant): Implement this. It will require some cleverness.
#
# _ToCSV testing
#
# TODO(mtennant): Implement tests for CSV output functionality.
#
# _ToHTML testing
#
# TODO(mtennant): Implement tests for HTML output functionality.
################
### MainTest ###
################
class MainTest(mox.MoxTestBase):
"""Test argument handling at the main method level."""
def setUp(self):
"""Setup for all tests in this class."""
mox.MoxTestBase.setUp(self)
def _StartCapturingOutput(self):
"""Begin capturing stdout and stderr."""
self._stdout = sys.stdout
self._stderr = sys.stderr
sys.stdout = self._stdout_cap = cStringIO.StringIO()
sys.stderr = self._stderr_cap = cStringIO.StringIO()
def _RetrieveCapturedOutput(self):
"""Return captured output so far as (stdout, stderr) tuple."""
try:
return (self._stdout_cap.getvalue(), self._stderr_cap.getvalue())
except AttributeError:
# This will happen if output capturing isn't on.
return None
def _StopCapturingOutput(self):
"""Stop capturing stdout and stderr."""
try:
sys.stdout = self._stdout
sys.stderr = self._stderr
except AttributeError:
# This will happen if output capturing wasn't on.
pass
def _PrepareArgv(self, *args):
"""Prepare command line for calling cros_portage_upgrade.main"""
sys.argv = [ re.sub("_unittest", "", sys.argv[0]) ]
sys.argv.extend(args)
def testHelp(self):
"""Test that --help is functioning"""
self._PrepareArgv("--help")
# Capture stdout/stderr so it can be verified later
self._StartCapturingOutput()
# Running with --help should exit with code==0
try:
cpu.main()
except exceptions.SystemExit, e:
self.assertEquals(e.args[0], 0)
# Verify that a message beginning with "Usage: " was printed
(stdout, stderr) = self._RetrieveCapturedOutput()
self._StopCapturingOutput()
self.assertTrue(stdout.startswith("Usage: "))
def testMissingBoard(self):
"""Test that running without --board exits with an error."""
self._PrepareArgv("")
# Capture stdout/stderr so it can be verified later
self._StartCapturingOutput()
# Running without --board should exit with code!=0
try:
cpu.main()
except exceptions.SystemExit, e:
self.assertNotEquals(e.args[0], 0)
# Verify that a message containing "ERROR: " was printed
(stdout, stderr) = self._RetrieveCapturedOutput()
self._StopCapturingOutput()
self.assertTrue("ERROR:" in stderr)
def testMissingPackage(self):
"""Test that running without a package argument exits with an error."""
self._PrepareArgv("--board=any-board")
# Capture stdout/stderr so it can be verified later
self._StartCapturingOutput()
# Running without a package should exit with code!=0
try:
cpu.main()
except exceptions.SystemExit, e:
self.assertNotEquals(e.args[0], 0)
# Verify that a message containing "ERROR: " was printed
(stdout, stderr) = self._RetrieveCapturedOutput()
self._StopCapturingOutput()
self.assertTrue("ERROR:" in stderr)
def testUpgraderRun(self):
"""Verify that running main method launches Upgrader.Run"""
self.mox.StubOutWithMock(cpu.Upgrader, 'Run')
cpu.Upgrader.Run()
self.mox.ReplayAll()
self._PrepareArgv("--board=any-board", "any-package")
cpu.main()
self.mox.VerifyAll()
if __name__ == '__main__':
unittest.main()