commit:     336ab90212c80ce9548362bf4fbdafd388c3515c
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Sun Sep  7 05:19:46 2014 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Thu Sep 11 21:36:08 2014 +0000
URL:        
http://sources.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=336ab902

depgraph._add_dep: fix bug #520950

This handles a case which occurs when
_solve_non_slot_operator_slot_conflicts calls _create_graph. In this
case, ignore unsatisfied deps for installed packages only if their depth
is beyond the depth requested by the user and the dep was initially
unsatisfied (not broken by a slot conflict in the current graph).

Since depth is meaningless for packages that are not reachable as deep
dependencies of arguments, the _UNREACHABLE_DEPTH constant is used as
the depth value for any packages added via _complete_graph. Also, any
sets added via _complete_graph have their reset_depth attribute set to
False.

The sys.stderr -> writemsg changes are necessary to ensure that the test
cases do not output unwanted error messages.

X-Gentoo-Bug: 520950
X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=520950

---
 pym/_emerge/depgraph.py                            | 128 ++++++++++++++++-----
 pym/portage/tests/resolver/ResolverPlayground.py   |  11 +-
 .../test_slot_conflict_unsatisfied_deep_deps.py    | 115 ++++++++++++++++++
 3 files changed, 226 insertions(+), 28 deletions(-)

diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py
index d6cd24d..cc87d9f 100644
--- a/pym/_emerge/depgraph.py
+++ b/pym/_emerge/depgraph.py
@@ -107,7 +107,7 @@ def _wildcard_set(atoms):
 
 class _frozen_depgraph_config(object):
 
-       def __init__(self, settings, trees, myopts, spinner):
+       def __init__(self, settings, trees, myopts, params, spinner):
                self.settings = settings
                self.target_root = settings["EROOT"]
                self.myopts = myopts
@@ -115,6 +115,7 @@ class _frozen_depgraph_config(object):
                if settings.get("PORTAGE_DEBUG", "") == "1":
                        self.edebug = 1
                self.spinner = spinner
+               self.requested_depth = params.get("deep", 0)
                self._running_root = trees[trees._running_eroot]["root_config"]
                self.pkgsettings = {}
                self.trees = {}
@@ -502,13 +503,18 @@ class _dynamic_depgraph_config(object):
 
 class depgraph(object):
 
+       # Represents the depth of a node that is unreachable from explicit
+       # user arguments (or their deep dependencies). Such nodes are pulled
+       # in by the _complete_graph method.
+       _UNREACHABLE_DEPTH = object()
+
        pkg_tree_map = RootConfig.pkg_tree_map
 
        def __init__(self, settings, trees, myopts, myparams, spinner,
                frozen_config=None, backtrack_parameters=BacktrackParameter(), 
allow_backtracking=False):
                if frozen_config is None:
                        frozen_config = _frozen_depgraph_config(settings, trees,
-                       myopts, spinner)
+                       myopts, myparams, spinner)
                self._frozen_config = frozen_config
                self._dynamic_config = _dynamic_depgraph_config(self, myparams,
                        allow_backtracking, backtrack_parameters)
@@ -2095,6 +2101,13 @@ class depgraph(object):
                                arg = arg_stack.pop()
                                if arg in traversed_set_args:
                                        continue
+
+                               # If a node with the same hash already exists in
+                               # the digraph, preserve the existing instance 
which
+                               # may have a different reset_depth attribute
+                               # (distiguishes user arguments from sets added 
for
+                               # another reason such as complete mode).
+                               arg = self._dynamic_config.digraph.get(arg, arg)
                                traversed_set_args.add(arg)
 
                                if add_to_digraph:
@@ -2114,8 +2127,16 @@ class depgraph(object):
                                        if nested_set is None:
                                                nested_set = 
root_config.sets.get(s)
                                        if nested_set is not None:
+                                               # Propagate the reset_depth 
attribute from
+                                               # parent set to nested set.
                                                nested_arg = SetArg(arg=token, 
pset=nested_set,
+                                                       
reset_depth=arg.reset_depth,
                                                        root_config=root_config)
+
+                                               # Preserve instances already in 
the graph (same
+                                               # reason as for the "arg" 
variable above).
+                                               nested_arg = 
self._dynamic_config.digraph.get(
+                                                       nested_arg, nested_arg)
                                                arg_stack.append(nested_arg)
                                                if add_to_digraph:
                                                        
self._dynamic_config.digraph.add(nested_arg, arg,
@@ -2164,9 +2185,42 @@ class depgraph(object):
                                dep.collapsed_priority.ignored):
                                # This is an unnecessary build-time dep.
                                return 1
+
+                       # NOTE: For removal actions, allow_unsatisfied is always
+                       # True since all existing removal actions traverse all
+                       # installed deps deeply via the _complete_graph method,
+                       # which calls _create_graph with allow_unsatisfied = 
True.
                        if allow_unsatisfied:
                                
self._dynamic_config._unsatisfied_deps.append(dep)
                                return 1
+
+                       # The following case occurs when
+                       # _solve_non_slot_operator_slot_conflicts calls
+                       # _create_graph. In this case, ignore unsatisfied deps 
for
+                       # installed packages only if their depth is beyond the 
depth
+                       # requested by the user and the dep was initially
+                       # unsatisfied (not broken by a slot conflict in the 
current
+                       # graph). See bug #520950.
+                       # NOTE: The value of dep.parent.depth is guaranteed to 
be
+                       # either an integer or _UNREACHABLE_DEPTH, where
+                       # _UNREACHABLE_DEPTH indicates that the parent has been
+                       # pulled in by the _complete_graph method (rather than 
by
+                       # explicit arguments or their deep dependencies). These
+                       # cases must be distinguished because depth is 
meaningless
+                       # for packages that are not reachable as deep 
dependencies
+                       # of arguments.
+                       if (self._dynamic_config._complete_mode and
+                               isinstance(dep.parent, Package) and
+                               dep.parent.installed and
+                               (dep.parent.depth is self._UNREACHABLE_DEPTH or
+                               (self._frozen_config.requested_depth is not 
True and
+                               dep.parent.depth >= 
self._frozen_config.requested_depth))):
+                               inst_pkg, in_graph = \
+                                       
self._select_pkg_from_installed(dep.root, dep.atom)
+                               if inst_pkg is None:
+                                       
self._dynamic_config._initially_unsatisfied_deps.append(dep)
+                                       return 1
+
                        
self._dynamic_config._unsatisfied_deps_for_display.append(
                                ((dep.root, dep.atom), {"myparent":dep.parent}))
 
@@ -2411,14 +2465,23 @@ class depgraph(object):
                # Installing package A, we need to make sure package A's deps 
are met.
                # emerge --deep <pkgspec>; we need to recursively check 
dependencies of pkgspec
                # If we are in --nodeps (no recursion) mode, we obviously only 
check 1 level of dependencies.
-               if arg_atoms and depth > 0:
+               if arg_atoms and depth != 0:
                        for parent, atom in arg_atoms:
                                if parent.reset_depth:
                                        depth = 0
                                        break
 
-               if previously_added and pkg.depth is not None:
-                       depth = min(pkg.depth, depth)
+               if previously_added and depth != 0 and \
+                       isinstance(pkg.depth, int):
+                       # Use pkg.depth if it is less than depth.
+                       if isinstance(depth, int):
+                               depth = min(pkg.depth, depth)
+                       else:
+                               # depth is _UNREACHABLE_DEPTH and pkg.depth is
+                               # an int, so use the int because it's considered
+                               # to be less than _UNREACHABLE_DEPTH.
+                               depth = pkg.depth
+
                pkg.depth = depth
                deep = self._dynamic_config.myparams.get("deep", 0)
                update = "--update" in self._frozen_config.myopts
@@ -2716,7 +2779,11 @@ class depgraph(object):
 
        def _wrapped_add_pkg_dep_string(self, pkg, dep_root, dep_priority,
                dep_string, allow_unsatisfied):
-               depth = pkg.depth + 1
+               if isinstance(pkg.depth, int):
+                       depth = pkg.depth + 1
+               else:
+                       depth = pkg.depth
+
                deep = self._dynamic_config.myparams.get("deep", 0)
                recurse_satisfied = deep is True or depth <= deep
                debug = "--debug" in self._frozen_config.myopts
@@ -3544,9 +3611,9 @@ class depgraph(object):
                                                if not 
self._add_pkg(arg.package, dep) or \
                                                        not 
self._create_graph():
                                                        if not 
self.need_restart():
-                                                               
sys.stderr.write(("\n\n!!! Problem " + \
+                                                               
writemsg(("\n\n!!! Problem " + \
                                                                        
"resolving dependencies for %s\n") % \
-                                                                       arg.arg)
+                                                                       
arg.arg, noiselevel=-1)
                                                        return 0, myfavorites
                                                continue
                                        if debug:
@@ -3947,10 +4014,14 @@ class depgraph(object):
                        # Recursively traversed virtual dependencies, and their
                        # direct dependencies, are considered to have the same
                        # depth as direct dependencies.
-                       if parent.depth is None:
-                               virt_depth = None
-                       else:
+                       if isinstance(parent.depth, int):
                                virt_depth = parent.depth + 1
+                       else:
+                               # The depth may be None when called via
+                               # _select_atoms_probe, or it may be
+                               # _UNREACHABLE_DEPTH for complete mode.
+                               virt_depth = parent.depth
+
                        chosen_atom_ids = frozenset(id(atom) for atom in 
mycheck[1])
                        selected_atoms = OrderedDict()
                        node_stack = [(parent, None, None)]
@@ -5833,14 +5904,14 @@ class depgraph(object):
                                        pset = root_config.sets[s]
                                atom = SETPREFIX + s
                                args.append(SetArg(arg=atom, pset=pset,
-                                       root_config=root_config))
+                                       reset_depth=False, 
root_config=root_config))
 
                self._set_args(args)
                for arg in self._expand_set_args(args, add_to_digraph=True):
                        for atom in arg.pset.getAtoms():
                                self._dynamic_config._dep_stack.append(
                                        Dependency(atom=atom, 
root=arg.root_config.root,
-                                               parent=arg))
+                                               parent=arg, 
depth=self._UNREACHABLE_DEPTH))
 
                if True:
                        if self._dynamic_config._ignored_deps:
@@ -7786,18 +7857,23 @@ class depgraph(object):
                                                break
 
                        if world_problems:
-                               sys.stderr.write("\n!!! Problems have been " + \
-                                       "detected with your world file\n")
-                               sys.stderr.write("!!! Please run " + \
-                                       green("emaint --check world")+"\n\n")
+                               writemsg("\n!!! Problems have been " + \
+                                       "detected with your world file\n",
+                                       noiselevel=-1)
+                               writemsg("!!! Please run " + \
+                                       green("emaint --check world")+"\n\n",
+                                       noiselevel=-1)
 
                if self._dynamic_config._missing_args:
-                       sys.stderr.write("\n" + colorize("BAD", "!!!") + \
-                               " Ebuilds for the following packages are either 
all\n")
-                       sys.stderr.write(colorize("BAD", "!!!") + \
-                               " masked or don't exist:\n")
-                       sys.stderr.write(" ".join(str(atom) for arg, atom in \
-                               self._dynamic_config._missing_args) + "\n")
+                       writemsg("\n" + colorize("BAD", "!!!") + \
+                               " Ebuilds for the following packages are either 
all\n",
+                               noiselevel=-1)
+                       writemsg(colorize("BAD", "!!!") + \
+                               " masked or don't exist:\n",
+                               noiselevel=-1)
+                       writemsg(" ".join(str(atom) for arg, atom in \
+                               self._dynamic_config._missing_args) + "\n",
+                               noiselevel=-1)
 
                if self._dynamic_config._pprovided_args:
                        arg_refs = {}
@@ -7837,7 +7913,7 @@ class depgraph(object):
                                msg.append("  C) Remove offending entries from 
package.provided.\n\n")
                                msg.append("The best course of action depends 
on the reason that an offending\n")
                                msg.append("package.provided entry exists.\n\n")
-                       sys.stderr.write("".join(msg))
+                       writemsg("".join(msg), noiselevel=-1)
 
                masked_packages = []
                for pkg in self._dynamic_config._masked_license_updates:
@@ -8487,7 +8563,7 @@ def _backtrack_depgraph(settings, trees, myopts, 
myparams, myaction, myfiles, sp
        backtracked = 0
 
        frozen_config = _frozen_depgraph_config(settings, trees,
-               myopts, spinner)
+               myopts, myparams, spinner)
 
        while backtracker:
 
@@ -8569,7 +8645,7 @@ def _resume_depgraph(settings, trees, mtimedb, myopts, 
myparams, spinner):
        mergelist = mtimedb["resume"]["mergelist"]
        dropped_tasks = {}
        frozen_config = _frozen_depgraph_config(settings, trees,
-               myopts, spinner)
+               myopts, myparams, spinner)
        while True:
                mydepgraph = depgraph(settings, trees,
                        myopts, myparams, spinner, frozen_config=frozen_config)

diff --git a/pym/portage/tests/resolver/ResolverPlayground.py 
b/pym/portage/tests/resolver/ResolverPlayground.py
index 9ee1d5e..3476aba 100644
--- a/pym/portage/tests/resolver/ResolverPlayground.py
+++ b/pym/portage/tests/resolver/ResolverPlayground.py
@@ -662,7 +662,8 @@ class ResolverPlaygroundTestCase(object):
                                                                        
str((node1, node2))) + \
                                                                        ", got: 
" + str(got))
 
-                       elif key in ("unstable_keywords", 
"needed_p_mask_changes") and expected is not None:
+                       elif key in ("unstable_keywords", 
"needed_p_mask_changes",
+                               "unsatisfied_deps") and expected is not None:
                                expected = set(expected)
 
                        if got != expected:
@@ -678,9 +679,10 @@ class ResolverPlaygroundResult(object):
 
        checks = (
                "success", "mergelist", "use_changes", "license_changes", 
"unstable_keywords", "slot_collision_solutions",
-               "circular_dependency_solutions", "needed_p_mask_changes",
+               "circular_dependency_solutions", "needed_p_mask_changes", 
"unsatisfied_deps",
                )
        optional_checks = (
+               "unsatisfied_deps"
                )
 
        def __init__(self, atoms, success, mydepgraph, favorites):
@@ -695,6 +697,7 @@ class ResolverPlaygroundResult(object):
                self.needed_p_mask_changes = None
                self.slot_collision_solutions = None
                self.circular_dependency_solutions = None
+               self.unsatisfied_deps = frozenset()
 
                if self.depgraph._dynamic_config._serialized_tasks_cache is not 
None:
                        self.mergelist = []
@@ -754,6 +757,10 @@ class ResolverPlaygroundResult(object):
                        sol = handler.solutions
                        self.circular_dependency_solutions = dict(zip([x.cpv 
for x in sol.keys()], sol.values()))
 
+               if self.depgraph._dynamic_config._unsatisfied_deps_for_display:
+                       self.unsatisfied_deps = set(dep_info[0][1]
+                               for dep_info in 
self.depgraph._dynamic_config._unsatisfied_deps_for_display)
+
 class ResolverPlaygroundDepcleanResult(object):
 
        checks = (

diff --git 
a/pym/portage/tests/resolver/test_slot_conflict_unsatisfied_deep_deps.py 
b/pym/portage/tests/resolver/test_slot_conflict_unsatisfied_deep_deps.py
new file mode 100644
index 0000000..13f7e67
--- /dev/null
+++ b/pym/portage/tests/resolver/test_slot_conflict_unsatisfied_deep_deps.py
@@ -0,0 +1,115 @@
+# Copyright 2014 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+from portage.tests import TestCase
+from portage.tests.resolver.ResolverPlayground import ResolverPlayground, 
ResolverPlaygroundTestCase
+
+class SlotConflictUnsatisfiedDeepDepsTestCase(TestCase):
+
+       def testSlotConflictUnsatisfiedDeepDeps(self):
+
+               ebuilds = {
+                       "dev-libs/A-1": { },
+                       "dev-libs/A-2": { "KEYWORDS": "~x86" },
+                       "dev-libs/B-1": { "DEPEND": "dev-libs/A" },
+                       "dev-libs/C-1": { "DEPEND": ">=dev-libs/A-2" },
+                       "dev-libs/D-1": { "DEPEND": "dev-libs/A" },
+               }
+
+               installed = {
+                       "dev-libs/broken-1": {
+                               "RDEPEND": "dev-libs/A 
dev-libs/initially-unsatisfied"
+                       },
+               }
+
+               world = (
+                       "dev-libs/A",
+                       "dev-libs/B",
+                       "dev-libs/C",
+                       "dev-libs/D",
+                       "dev-libs/broken"
+               )
+
+               test_cases = (
+                       # Test bug #520950, where unsatisfied deps of installed
+                       # packages are supposed to be ignored when they are 
beyond
+                       # the depth requested by the user.
+                       ResolverPlaygroundTestCase(
+                               ["dev-libs/B", "dev-libs/C", "dev-libs/D"],
+                               all_permutations=True,
+                               options={
+                                       "--autounmask": "y",
+                                       "--complete-graph": True
+                               },
+                               mergelist=["dev-libs/A-2", "dev-libs/B-1", 
"dev-libs/C-1", "dev-libs/D-1"],
+                               ignore_mergelist_order=True,
+                               unstable_keywords=["dev-libs/A-2"],
+                               unsatisfied_deps=[],
+                               success=False),
+
+                       ResolverPlaygroundTestCase(
+                               ["@world"],
+                               options={
+                                       "--autounmask": "y",
+                                       "--complete-graph": True
+                               },
+                               mergelist=["dev-libs/A-2", "dev-libs/B-1", 
"dev-libs/C-1", "dev-libs/D-1"],
+                               ignore_mergelist_order=True,
+                               unstable_keywords=["dev-libs/A-2"],
+                               unsatisfied_deps=["dev-libs/broken"],
+                               success=False),
+
+                       # Test --selective with --deep = 0
+                       ResolverPlaygroundTestCase(
+                               ["@world"],
+                               options={
+                                       "--autounmask": "y",
+                                       "--complete-graph": True,
+                                       "--selective": True,
+                                       "--deep": 0
+                               },
+                               mergelist=["dev-libs/A-2", "dev-libs/B-1", 
"dev-libs/C-1", "dev-libs/D-1"],
+                               ignore_mergelist_order=True,
+                               unstable_keywords=["dev-libs/A-2"],
+                               unsatisfied_deps=[],
+                               success=False),
+
+                       # Test --deep = 1
+                       ResolverPlaygroundTestCase(
+                               ["@world"],
+                               options={
+                                       "--autounmask": "y",
+                                       "--complete-graph": True,
+                                       "--selective": True,
+                                       "--deep": 1
+                               },
+                               mergelist=["dev-libs/A-2", "dev-libs/B-1", 
"dev-libs/C-1", "dev-libs/D-1"],
+                               ignore_mergelist_order=True,
+                               unstable_keywords=["dev-libs/A-2"],
+                               
unsatisfied_deps=["dev-libs/initially-unsatisfied"],
+                               success=False),
+
+                       # Test --deep = True
+                       ResolverPlaygroundTestCase(
+                               ["@world"],
+                               options={
+                                       "--autounmask": "y",
+                                       "--complete-graph": True,
+                                       "--selective": True,
+                                       "--deep": True
+                               },
+                               mergelist=["dev-libs/A-2", "dev-libs/B-1", 
"dev-libs/C-1", "dev-libs/D-1"],
+                               ignore_mergelist_order=True,
+                               unstable_keywords=["dev-libs/A-2"],
+                               
unsatisfied_deps=["dev-libs/initially-unsatisfied"],
+                               success=False),
+               )
+
+               playground = ResolverPlayground(ebuilds=ebuilds, 
installed=installed,
+                       world=world, debug=False)
+               try:
+                       for test_case in test_cases:
+                               playground.run_TestCase(test_case)
+                               self.assertEqual(test_case.test_success, True, 
test_case.fail_msg)
+               finally:
+                       playground.cleanup()

Reply via email to