--- a/src/modules/client/pkg_solver.py Mon Apr 28 12:58:40 2014 -0700
+++ b/src/modules/client/pkg_solver.py Sat Apr 12 23:31:42 2014 -0700
@@ -23,6 +23,7 @@
#
# Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
#
+import operator
import os
import time
@@ -1102,9 +1103,9 @@
pass
mver = fmri.version
- # Always use a copy; return value may be cached.
- all_fmris = self.__get_catalog_fmris(fmri.pkg_name)[:]
- all_fmris.reverse()
+ # Always use a copy in reverse order (so versions are in
+ # descending order); return value may be cached.
+ all_fmris = self.__get_catalog_fmris(fmri.pkg_name)[::-1]
# frozensets are used so callers don't inadvertently
# update these sets (which may be cached). Iteration is
@@ -1116,7 +1117,6 @@
for i, f in enumerate(all_fmris):
if mver == f.version:
last_ver = i
- continue
elif last_ver is not None:
break
@@ -1135,6 +1135,103 @@
self.__cache[tp] = (matching, remaining)
return self.__cache[tp]
+ def __comb_common_noversion(self, fmri, dotrim, constraint,
+ obsolete_ok):
+ """Implements versionless comb logic."""
+
+ all_fmris = self.__get_catalog_fmris(fmri.pkg_name)
+ matching = frozenset((
+ f
+ for f in all_fmris
+ if not dotrim or f not in self.__trim_dict
+ if obsolete_ok or not self.__fmri_is_obsolete(f)
+ ))
+ remaining = frozenset(set(all_fmris) - matching)
+ return matching, remaining
+
+ def __comb_common_version(self, fmri, dotrim, constraint, obsolete_ok):
+ """Implements versioned comb logic."""
+
+ # If using a version constraint that cares about branch (but not
+ # timestr), the fmris will have to be resorted so that the
+ # version chopping done here works as expected. This is because
+ # version sort order is release, branch, timestr which is
+ # different than is_successor() order. However, if the provided
+ # FMRI has a timestamp, doesn't have a branch, or we're applying
+ # a constraint that doesn't care about the branch, then we don't
+ # need to resort.
+ branch_sort = not fmri.version.timestr and \
+ fmri.version.branch and \
+ constraint not in (version.CONSTRAINT_NONE,
+ version.CONSTRAINT_RELEASE,
+ version.CONSTRAINT_RELEASE_MAJOR,
+ version.CONSTRAINT_RELEASE_MINOR)
+
+ all_fmris = self.__get_catalog_fmris(fmri.pkg_name)
+ mver = fmri.version
+ if branch_sort:
+ # The first version of this attempted to perform
+ # multiple passes to avoid the cost of sorting by
+ # finding the last entry that matched CONSTRAINT_RELEASE
+ # and then only resorting the slice of comb_fmris from
+ # first_ver to last_ver, but that actually ended up
+ # being slower because multiple passes with is_successor()
+ # (even over a small portion of comb_fmris) is more
+ # expensive than simply resorting the entire list.
+ # Ideally, we'd get the entries from
+ # __get_catalog_fmris() in this order already which
+ # would be faster since we'd avoid a second sort.
+ skey = operator.attrgetter(
+ 'version.branch', 'version.release')
+ # Always use a copy; return value may be cached.
+ comb_fmris = sorted(all_fmris, key=skey,
+ reverse=True)
+ else:
+ # Always use a copy; return value may be cached.
+ comb_fmris = all_fmris[::-1]
+
+ # Iteration is performed in descending version order with the
+ # assumption that systems are generally up-to-date so it should
+ # be faster to start at the end and look for the oldest version
+ # that matches.
+ first_ver = None
+ last_ver = None
+ for i, f in enumerate(comb_fmris):
+ fver = f.version
+ if ((fver.is_successor(mver, constraint=constraint) or
+ fver == mver)):
+ if first_ver is None:
+ first_ver = i
+ last_ver = i
+ elif last_ver is not None:
+ break
+
+ if last_ver is not None:
+ # Oddly enough, it's a little bit faster to iterate
+ # through the slice of comb_fmris again and store
+ # matches here instead of above. Perhaps variable
+ # scoping overhead is to blame?
+ matching = []
+ remaining = []
+ for f in comb_fmris[first_ver:last_ver + 1]:
+ if ((not dotrim or
+ f not in self.__trim_dict) and
+ (obsolete_ok or not
+ self.__fmri_is_obsolete(f))):
+ matching.append(f)
+ else:
+ remaining.append(f)
+ matching = frozenset(matching)
+ remaining = frozenset(chain(
+ comb_fmris[:first_ver],
+ remaining,
+ comb_fmris[last_ver + 1:]))
+ else:
+ matching = frozenset()
+ remaining = frozenset(comb_fmris)
+
+ return matching, remaining
+
def __comb_common(self, fmri, dotrim, constraint, obsolete_ok):
"""Underlying impl. of other comb routines"""
@@ -1145,62 +1242,14 @@
if (not self.__trimdone and dotrim) or tp not in self.__cache:
# use frozensets so callers don't inadvertently update
# these sets (which may be cached).
- all_fmris = self.__get_catalog_fmris(fmri.pkg_name)
- mver = fmri.version
- if not mver:
- matching = frozenset((
- f
- for f in all_fmris
- if not dotrim or f not in self.__trim_dict
- if obsolete_ok or not self.__fmri_is_obsolete(f)
- ))
- remaining = frozenset(set(all_fmris) - matching)
+ if not fmri.version or not fmri.version.release:
+ matching, remaining = \
+ self.__comb_common_noversion(fmri, dotrim,
+ constraint, obsolete_ok)
else:
- # Always use a copy; return value may be cached.
- all_fmris = all_fmris[:]
- all_fmris.reverse()
-
- # Iteration is performed in descending version
- # order with the assumption that systems are
- # generally up-to-date so it should be faster to
- # start at the end and look for the oldest
- # version that matches.
- first_ver = None
- last_ver = None
- for i, f in enumerate(all_fmris):
- fver = f.version
- if (fver.is_successor(mver,
- constraint=constraint) or \
- fver == mver):
- if first_ver is None:
- first_ver = i
- last_ver = i
- elif last_ver is not None:
- break
-
- if last_ver is not None:
- # Oddly enough, it's a little bit faster
- # to iterate through the slice of
- # all_fmris again and store matches here
- # instead of above. Perhaps variable
- # scoping overhead is to blame?
- matching = []
- remaining = []
- for f in all_fmris[first_ver:last_ver + 1]:
- if ((not dotrim or
- f not in self.__trim_dict) and
- (obsolete_ok or not
- self.__fmri_is_obsolete(f))):
- matching.append(f)
- else:
- remaining.append(f)
- matching = frozenset(matching)
- remaining = frozenset(chain(remaining,
- all_fmris[:first_ver],
- all_fmris[last_ver + 1:]))
- else:
- matching = frozenset()
- remaining = frozenset(all_fmris)
+ matching, remaining = \
+ self.__comb_common_version(fmri, dotrim,
+ constraint, obsolete_ok)
# if we haven't finished trimming, don't cache this
if not self.__trimdone:
--- a/src/tests/cli/t_pkg_install.py Mon Apr 28 12:58:40 2014 -0700
+++ b/src/tests/cli/t_pkg_install.py Sat Apr 12 23:31:42 2014 -0700
@@ -7648,6 +7648,7 @@
self.pkg("update -v")
self.pkg("list inc2p2", exit=1)
+
class TestObsoletionNestedIncorporations(pkg5unittest.SingleDepotTestCase):
# Only start/stop the depot once (instead of for every test)
@@ -7779,6 +7780,76 @@
self.pkg("%s stem" % install_cmd, exit=1)
+class TestPkgInstallMultiIncorp(pkg5unittest.ManyDepotTestCase):
+ """Tests involving incorporations and multiple publishers."""
+
+ incorporated_latest = """
+ open [email protected]
+ close
+ open [email protected]
+ close"""
+
+ incorporated = """
+ open [email protected]
+ close
+ open [email protected]
+ close """
+
+ incorporates = """
+ open [email protected]
+ add depend type=incorporate [email protected]
+ close
+ open [email protected]
+ add depend type=incorporate [email protected]
+ close"""
+
+ persistent_setup = True
+
+ def setUp(self):
+ pkg5unittest.ManyDepotTestCase.setUp(self, ["test1", "test2"])
+ self.rurl1 = self.dcs[1].get_repo_url()
+ self.rurl2 = self.dcs[2].get_repo_url()
+
+ def test_1_incorp_latest_older(self):
+ """Ensure that if the newest release version of a package is
+ available for an older branch that incorporate dependencies work
+ as expected."""
+
+ self.image_create(self.rurl1)
+ self.pkgsend_bulk(self.rurl1, (self.incorporates,
+ self.incorporated, self.incorporated_latest))
+
+ # First, install two incorporations that intersect such that
+ # only the version before the latest branch can be installed.
+ self.pkg("install userland-incorporation vim-incorporation")
+
+ # Then, attempt to install vim; this should succeed even though
+ # the newest version available is for an older branch.
+ self.pkg("install [email protected]")
+
+ def test_2_incorp_multi_pub(self):
+ """Ensure that if another publisher offers newer packages that
+ satisfy an incorporate dependency, but are rejected because of
+ publisher selection, that the preferred publisher's package can
+ still satisfy the incorporate."""
+
+ self.image_create(self.rurl1)
+ self.pkgsend_bulk(self.rurl1, (self.incorporates,
+ self.incorporated))
+ self.pkgsend_bulk(self.rurl2, self.incorporated_latest)
+
+ # First, install the incorporation.
+ self.pkg("install userland-incorporation")
+
+ # Next, add the second publisher.
+ self.pkg("set-publisher -p %s" % self.rurl2)
+
+ # Next, verify that first publisher's incorporated package can
+ # be installed since it satisfies incorporate dependencies even
+ # though second publisher's versions will be rejected.
+ self.pkg("install //test1/vim")
+
+
class TestPkgInstallLicense(pkg5unittest.SingleDepotTestCase):
"""Tests involving one or more packages that require license acceptance
or display."""