public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
From: "Zac Medico" <zmedico@gentoo.org>
To: gentoo-commits@lists.gentoo.org
Subject: [gentoo-commits] proj/portage:master commit in: pym/portage/tests/resolver/, pym/_emerge/
Date: Thu, 11 Sep 2014 21:37:12 +0000 (UTC)	[thread overview]
Message-ID: <1410471368.336ab90212c80ce9548362bf4fbdafd388c3515c.zmedico@gentoo> (raw)

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	other threads:[~2014-09-11 21:37 UTC|newest]

Thread overview: 56+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-09-11 21:37 Zac Medico [this message]
  -- strict thread matches above, loose matches on Subject: below --
2018-05-04 17:12 [gentoo-commits] proj/portage:master commit in: pym/portage/tests/resolver/, pym/_emerge/ Zac Medico
2018-04-12  2:45 Zac Medico
2017-09-29 17:24 Zac Medico
2017-06-02  5:41 Zac Medico
2017-04-01  5:48 Zac Medico
2017-03-22  8:59 Zac Medico
2017-03-16  4:51 Zac Medico
2017-03-09 19:36 Zac Medico
2016-08-07 17:55 Zac Medico
2015-11-24 16:45 Zac Medico
2014-11-16  9:04 Zac Medico
2014-10-27  9:26 Zac Medico
2014-09-19  9:28 Zac Medico
2014-09-19  9:17 Zac Medico
2014-09-17 16:35 Zac Medico
2014-09-16 21:04 Brian Dolbec
2014-04-26 19:44 Sebastian Luther
2014-02-16 17:25 Sebastian Luther
2014-02-15 12:40 Sebastian Luther
2014-02-05 19:42 Sebastian Luther
2014-01-07 22:22 Arfrever Frehtes Taifersar Arahesis
2013-12-05 15:38 Brian Dolbec
2013-12-01 10:19 Brian Dolbec
2013-11-27  7:44 Mike Frysinger
2013-08-02  8:26 Zac Medico
2013-07-07 19:16 Zac Medico
2013-07-06 21:45 Zac Medico
2013-03-19 21:06 Zac Medico
2013-03-05  0:56 Zac Medico
2013-02-14  4:45 Zac Medico
2013-02-12  2:50 Zac Medico
2013-02-11 22:51 Zac Medico
2013-02-11  1:58 Zac Medico
2012-12-01 23:23 Zac Medico
2012-10-26  6:06 Zac Medico
2012-10-26  4:57 Zac Medico
2012-07-05  3:16 Zac Medico
2012-06-15 23:04 Zac Medico
2012-02-26 10:00 Zac Medico
2011-11-18  1:26 Zac Medico
2011-09-30  8:30 Zac Medico
2011-09-19  3:05 Zac Medico
2011-09-18 20:08 Zac Medico
2011-09-18 19:42 Zac Medico
2011-09-15  5:10 Zac Medico
2011-09-11 20:43 Zac Medico
2011-06-12 22:13 Zac Medico
2011-05-24 23:59 Zac Medico
2011-05-23  5:40 Zac Medico
2011-05-22 23:49 Zac Medico
2011-05-21  3:49 Zac Medico
2011-05-03 22:59 Zac Medico
2011-04-27 20:40 Zac Medico
2011-02-13 13:55 Zac Medico
2011-02-13 10:23 Zac Medico

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1410471368.336ab90212c80ce9548362bf4fbdafd388c3515c.zmedico@gentoo \
    --to=zmedico@gentoo.org \
    --cc=gentoo-commits@lists.gentoo.org \
    --cc=gentoo-dev@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox