Gentoo Archives: gentoo-commits

From: Sebastian Luther <SebastianLuther@×××.de>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: pym/portage/tests/resolver/, pym/_emerge/
Date: Wed, 05 Feb 2014 19:42:19
Message-Id: 1391629161.a862cc5dd1a56114fa579c5fb01b518b243666d9.few@gentoo
1 commit: a862cc5dd1a56114fa579c5fb01b518b243666d9
2 Author: Sebastian Luther <SebastianLuther <AT> gmx <DOT> de>
3 AuthorDate: Tue Jan 28 21:32:25 2014 +0000
4 Commit: Sebastian Luther <SebastianLuther <AT> gmx <DOT> de >
5 CommitDate: Wed Feb 5 19:39:21 2014 +0000
6 URL: http://git.overlays.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=a862cc5d
7
8 Solve some slot conflicts without backtracking
9
10 ---
11 pym/_emerge/depgraph.py | 345 ++++++++++++++++++++-
12 pym/portage/tests/resolver/test_backtracking.py | 13 +-
13 pym/portage/tests/resolver/test_blocker.py | 48 +++
14 pym/portage/tests/resolver/test_slot_collisions.py | 75 ++++-
15 4 files changed, 457 insertions(+), 24 deletions(-)
16
17 diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py
18 index 1bb086b..ae6b883 100644
19 --- a/pym/_emerge/depgraph.py
20 +++ b/pym/_emerge/depgraph.py
21 @@ -3,6 +3,7 @@
22
23 from __future__ import print_function, unicode_literals
24
25 +import collections
26 import errno
27 import io
28 import logging
29 @@ -78,7 +79,7 @@ from _emerge.resolver.backtracking import Backtracker, BacktrackParameter
30 from _emerge.resolver.package_tracker import PackageTracker, PackageTrackerDbapiWrapper
31 from _emerge.resolver.slot_collision import slot_conflict_handler
32 from _emerge.resolver.circular_dependency import circular_dependency_handler
33 -from _emerge.resolver.output import Display
34 +from _emerge.resolver.output import Display, format_unmatched_atom
35
36 if sys.hexversion >= 0x3000000:
37 basestring = str
38 @@ -423,6 +424,8 @@ class _dynamic_depgraph_config(object):
39 self._complete_mode = False
40 self._slot_operator_deps = {}
41 self._package_tracker = PackageTracker()
42 + # Track missed updates caused by solved conflicts.
43 + self._conflict_missed_update = collections.defaultdict(dict)
44
45 for myroot in depgraph._frozen_config.trees:
46 self.sets[myroot] = _depgraph_sets()
47 @@ -769,7 +772,8 @@ class depgraph(object):
48 # missed update from each SLOT.
49 missed_updates = {}
50 for pkg, mask_reasons in \
51 - self._dynamic_config._runtime_pkg_mask.items():
52 + chain(self._dynamic_config._runtime_pkg_mask.items(),
53 + self._dynamic_config._conflict_missed_update.items()):
54 if pkg.installed:
55 # Exclude installed here since we only
56 # want to show available updates.
57 @@ -779,7 +783,8 @@ class depgraph(object):
58 for chosen_pkg in self._dynamic_config._package_tracker.match(
59 pkg.root, pkg.slot_atom):
60 any_selected = True
61 - if chosen_pkg >= pkg:
62 + if chosen_pkg > pkg or (not chosen_pkg.installed and \
63 + chosen_pkg.version == pkg.version):
64 missed_update = False
65 break
66 if any_selected and missed_update:
67 @@ -869,7 +874,7 @@ class depgraph(object):
68
69 self._show_merge_list()
70 msg = []
71 - msg.append("\nWARNING: One or more updates have been " + \
72 + msg.append("\nWARNING: One or more updates/rebuilds have been " + \
73 "skipped due to a dependency conflict:\n\n")
74
75 indent = " "
76 @@ -879,22 +884,29 @@ class depgraph(object):
77 msg.append(" for %s" % (pkg.root,))
78 msg.append("\n\n")
79
80 - for parent, atom in parent_atoms:
81 - msg.append(indent)
82 - msg.append(str(pkg))
83 + msg.append(indent)
84 + msg.append(str(pkg))
85 + msg.append(" conflicts with\n")
86
87 - msg.append(" conflicts with\n")
88 - msg.append(2*indent)
89 + for parent, atom in parent_atoms:
90 if isinstance(parent,
91 (PackageArg, AtomArg)):
92 # For PackageArg and AtomArg types, it's
93 # redundant to display the atom attribute.
94 + msg.append(2*indent)
95 msg.append(str(parent))
96 + msg.append("\n")
97 else:
98 # Display the specific atom from SetArg or
99 # Package types.
100 - msg.append("%s required by %s" % (atom, parent))
101 - msg.append("\n")
102 + atom, marker = format_unmatched_atom(
103 + pkg, atom, self._pkg_use_enabled)
104 +
105 + msg.append(2*indent)
106 + msg.append("%s required by %s\n" % (atom, parent))
107 + msg.append(2*indent)
108 + msg.append(marker)
109 + msg.append("\n")
110 msg.append("\n")
111
112 writemsg("".join(msg), noiselevel=-1)
113 @@ -956,6 +968,239 @@ class depgraph(object):
114 writemsg(line + '\n', noiselevel=-1)
115 writemsg('\n', noiselevel=-1)
116
117 + def _solve_non_slot_operator_slot_conflicts(self):
118 + """
119 + This function solves slot conflicts which can
120 + be solved by simply choosing one of the conflicting
121 + and removing all the other ones.
122 + It is able to solve somewhat more complex cases where
123 + conflicts can only be solved simultaniously.
124 + """
125 + debug = "--debug" in self._frozen_config.myopts
126 +
127 + # List all conflicts. Ignore those that involve slot operator rebuilds
128 + # as the logic there needs special slot conflict behavior which isn't
129 + # provided by this function.
130 + conflicts = []
131 + for conflict in self._dynamic_config._package_tracker.slot_conflicts():
132 + slot_key = conflict.root, conflict.atom
133 + if slot_key not in self._dynamic_config._slot_operator_replace_installed:
134 + conflicts.append(conflict)
135 +
136 + if not conflicts:
137 + return
138 +
139 + # Get a set of all conflicting packages.
140 + conflict_pkgs = set()
141 + for conflict in conflicts:
142 + conflict_pkgs.update(conflict)
143 +
144 + # Get the list of other packages which are only
145 + # required by conflict packages.
146 + indirect_conflict_candidates = set()
147 + for pkg in conflict_pkgs:
148 + indirect_conflict_candidates.update(self._dynamic_config.digraph.child_nodes(pkg))
149 + indirect_conflict_candidates.difference_update(conflict_pkgs)
150 +
151 + indirect_conflict_pkgs = set()
152 + while indirect_conflict_candidates:
153 + pkg = indirect_conflict_candidates.pop()
154 +
155 + only_conflict_parents = True
156 + for parent, atom in self._dynamic_config._parent_atoms.get(pkg, []):
157 + if parent not in conflict_pkgs and parent not in indirect_conflict_pkgs:
158 + only_conflict_parents = False
159 + break
160 + if not only_conflict_parents:
161 + continue
162 +
163 + indirect_conflict_pkgs.add(pkg)
164 + for child in self._dynamic_config.digraph.child_nodes(pkg):
165 + if child in conflict_pkgs or child in indirect_conflict_pkgs:
166 + continue
167 + indirect_conflict_candidates.add(child)
168 +
169 + # Create a graph containing the conflict packages
170 + # and a special 'non_conflict_node' that represents
171 + # all non-conflict packages.
172 + conflict_graph = digraph()
173 +
174 + non_conflict_node = "(non-conflict package)"
175 + conflict_graph.add(non_conflict_node, None)
176 +
177 + for pkg in chain(conflict_pkgs, indirect_conflict_pkgs):
178 + conflict_graph.add(pkg, None)
179 +
180 + # Add parent->child edges for each conflict package.
181 + # Parents, which aren't conflict packages are represented
182 + # by 'non_conflict_node'.
183 + # If several conflicting packages are matched, but not all,
184 + # add a tuple with the matched packages to the graph.
185 + class or_tuple(tuple):
186 + """
187 + Helper class for debug printing.
188 + """
189 + def __str__(self):
190 + return "(%s)" % ",".join(str(pkg) for pkg in self)
191 +
192 + for conflict in conflicts:
193 + all_parent_atoms = set()
194 + for pkg in conflict:
195 + all_parent_atoms.update(
196 + self._dynamic_config._parent_atoms.get(pkg, []))
197 +
198 + for parent, atom in all_parent_atoms:
199 + is_arg_parent = isinstance(parent, AtomArg)
200 +
201 + if parent not in conflict_pkgs and \
202 + parent not in indirect_conflict_pkgs:
203 + parent = non_conflict_node
204 +
205 + atom_set = InternalPackageSet(
206 + initial_atoms=(atom,), allow_repo=True)
207 +
208 + matched = []
209 + for pkg in conflict:
210 + if atom_set.findAtomForPackage(pkg, \
211 + modified_use=self._pkg_use_enabled(pkg)) and \
212 + not (is_arg_parent and pkg.installed):
213 + matched.append(pkg)
214 + if len(matched) == len(conflict):
215 + # All packages match.
216 + continue
217 + elif len(matched) == 1:
218 + conflict_graph.add(matched[0], parent)
219 + else:
220 + # More than one packages matched, but not all.
221 + conflict_graph.add(or_tuple(matched), parent)
222 +
223 + for pkg in indirect_conflict_pkgs:
224 + for parent, atom in self._dynamic_config._parent_atoms.get(pkg, []):
225 + if parent not in conflict_pkgs and \
226 + parent not in indirect_conflict_pkgs:
227 + parent = non_conflict_node
228 + conflict_graph.add(pkg, parent)
229 +
230 + if debug:
231 + writemsg_level(
232 + "\n!!! Slot conflict graph:\n",
233 + level=logging.DEBUG, noiselevel=-1)
234 + conflict_graph.debug_print()
235 +
236 + # Now select required packages. Collect them in the
237 + # 'forced' set.
238 + forced = set([non_conflict_node])
239 + unexplored = set([non_conflict_node])
240 + # or_tuples get special handling. We first explore
241 + # all packages in the hope of having forced one of
242 + # the packages in the tuple. This way we don't have
243 + # to choose one.
244 + unexplored_tuples = set()
245 +
246 + while unexplored:
247 + # Handle all unexplored packages.
248 + while unexplored:
249 + node = unexplored.pop()
250 + for child in conflict_graph.child_nodes(node):
251 + if child in forced:
252 + continue
253 + forced.add(child)
254 + if isinstance(child, Package):
255 + unexplored.add(child)
256 + else:
257 + unexplored_tuples.add(child)
258 +
259 + # Now handle unexplored or_tuples. Move on with packages
260 + # once we had to choose one.
261 + while unexplored_tuples:
262 + nodes = unexplored_tuples.pop()
263 + if any(node in forced for node in nodes):
264 + # At least one of the packages in the
265 + # tuple is already forced, which means the
266 + # dependency represented by this tuple
267 + # is satisfied.
268 + continue
269 +
270 + # We now have to choose one of packages in the tuple.
271 + # In theory one could solve more conflicts if we'd be
272 + # able to try different choices here, but that has lots
273 + # of other problems. For now choose the package that was
274 + # pulled first, as this should be the most desirable choice
275 + # (otherwise it wouldn't have been the first one).
276 + forced.add(nodes[0])
277 + unexplored.add(nodes[0])
278 + break
279 +
280 + # Remove 'non_conflict_node' and or_tuples from 'forced'.
281 + forced = set(pkg for pkg in forced if isinstance(pkg, Package))
282 + non_forced = set(pkg for pkg in conflict_pkgs if pkg not in forced)
283 +
284 + if debug:
285 + writemsg_level(
286 + "\n!!! Slot conflict solution:\n",
287 + level=logging.DEBUG, noiselevel=-1)
288 + for conflict in conflicts:
289 + writemsg_level(
290 + " Conflict: (%s, %s)\n" % (conflict.root, conflict.atom),
291 + level=logging.DEBUG, noiselevel=-1)
292 + for pkg in conflict:
293 + if pkg in forced:
294 + writemsg_level(
295 + " keep: %s\n" % pkg,
296 + level=logging.DEBUG, noiselevel=-1)
297 + else:
298 + writemsg_level(
299 + " remove: %s\n" % pkg,
300 + level=logging.DEBUG, noiselevel=-1)
301 +
302 + broken_packages = set()
303 + for pkg in non_forced:
304 + for parent, atom in self._dynamic_config._parent_atoms.get(pkg, []):
305 + if isinstance(parent, Package) and parent not in non_forced:
306 + # Non-forcing set args are expected to be a parent of all
307 + # packages in the conflict.
308 + broken_packages.add(parent)
309 + self._remove_pkg(pkg)
310 +
311 + # Process the dependencies of choosen conflict packages
312 + # again to properly account for blockers.
313 + broken_packages.update(forced)
314 +
315 + # Filter out broken packages which have been removed during
316 + # recursive removal in self._remove_pkg.
317 + broken_packages = list(pkg for pkg in broken_packages if pkg in broken_packages \
318 + if self._dynamic_config._package_tracker.contains(pkg, installed=False))
319 +
320 + self._dynamic_config._dep_stack.extend(broken_packages)
321 +
322 + if broken_packages:
323 + # Process dependencies. This cannot fail because we just ensured that
324 + # the remaining packages satisfy all dependencies.
325 + self._create_graph()
326 +
327 + # Record missed updates.
328 + for conflict in conflicts:
329 + if not any(pkg in non_forced for pkg in conflict):
330 + continue
331 + for pkg in conflict:
332 + if pkg not in non_forced:
333 + continue
334 +
335 + for other in conflict:
336 + if other is pkg:
337 + continue
338 +
339 + for parent, atom in self._dynamic_config._parent_atoms.get(other, []):
340 + atom_set = InternalPackageSet(
341 + initial_atoms=(atom,), allow_repo=True)
342 + if not atom_set.findAtomForPackage(pkg,
343 + modified_use=self._pkg_use_enabled(pkg)):
344 + self._dynamic_config._conflict_missed_update[pkg].setdefault(
345 + "slot conflict", set())
346 + self._dynamic_config._conflict_missed_update[pkg]["slot conflict"].add(
347 + (parent, atom))
348 +
349 +
350 def _process_slot_conflicts(self):
351 """
352 If there are any slot conflicts and backtracking is enabled,
353 @@ -963,6 +1208,9 @@ class depgraph(object):
354 is called, so that all relevant reverse dependencies are
355 available for use in backtracking decisions.
356 """
357 +
358 + self._solve_non_slot_operator_slot_conflicts()
359 +
360 for conflict in self._dynamic_config._package_tracker.slot_conflicts():
361 self._process_slot_conflict(conflict)
362
363 @@ -1286,9 +1534,29 @@ class depgraph(object):
364 selective = "selective" in self._dynamic_config.myparams
365 want_downgrade = None
366
367 + def check_reverse_dependencies(existing_pkg, candidate_pkg):
368 + """
369 + Check if candidate_pkg satisfies all of existing_pkg's non-
370 + slot operator parents.
371 + """
372 + for parent, atom in self._dynamic_config._parent_atoms.get(existing_pkg, []):
373 + if atom.slot_operator == "=" and parent.built:
374 + continue
375 +
376 + atom_set = InternalPackageSet(initial_atoms=(atom,),
377 + allow_repo=True)
378 + if not atom_set.findAtomForPackage(candidate_pkg,
379 + modified_use=self._pkg_use_enabled(candidate_pkg)):
380 + return False
381 + return True
382 +
383 +
384 for replacement_parent in self._iter_similar_available(dep.parent,
385 dep.parent.slot_atom, autounmask_level=autounmask_level):
386
387 + if not check_reverse_dependencies(dep.parent, replacement_parent):
388 + continue
389 +
390 selected_atoms = None
391
392 atoms = set()
393 @@ -1389,10 +1657,11 @@ class depgraph(object):
394 if unevaluated_atom not in selected_atoms:
395 continue
396
397 - if not insignificant:
398 + if not insignificant and \
399 + check_reverse_dependencies(dep.child, pkg):
400 +
401 candidate_pkg_atoms.append((pkg, unevaluated_atom))
402 candidate_pkgs.append(pkg)
403 -
404 replacement_candidates.append(candidate_pkg_atoms)
405 if all_candidate_pkgs is None:
406 all_candidate_pkgs = set(candidate_pkgs)
407 @@ -2113,6 +2382,56 @@ class depgraph(object):
408 dep_stack.append(pkg)
409 return 1
410
411 +
412 + def _remove_pkg(self, pkg):
413 + """
414 + Remove a package and all its then parentless digraph
415 + children from all depgraph datastructures.
416 + """
417 + try:
418 + children = self._dynamic_config.digraph.child_nodes(pkg)
419 + self._dynamic_config.digraph.remove(pkg)
420 + except KeyError:
421 + children = []
422 +
423 + self._dynamic_config._package_tracker.discard_pkg(pkg)
424 +
425 + self._dynamic_config._parent_atoms.pop(pkg, None)
426 + self._dynamic_config._set_nodes.discard(pkg)
427 +
428 + for child in children:
429 + try:
430 + self._dynamic_config._parent_atoms[child] = set((parent, atom) \
431 + for (parent, atom) in self._dynamic_config._parent_atoms[child] \
432 + if parent is not pkg)
433 + except KeyError:
434 + pass
435 +
436 + # Remove slot operator dependencies.
437 + slot_key = (pkg.root, pkg.slot_atom)
438 + if slot_key in self._dynamic_config._slot_operator_deps:
439 + self._dynamic_config._slot_operator_deps[slot_key] = \
440 + [dep for dep in self._dynamic_config._slot_operator_deps[slot_key] \
441 + if dep.child is not pkg]
442 + if not self._dynamic_config._slot_operator_deps[slot_key]:
443 + del self._dynamic_config._slot_operator_deps[slot_key]
444 +
445 + # Remove blockers.
446 + self._dynamic_config._blocker_parents.discard(pkg)
447 + self._dynamic_config._irrelevant_blockers.discard(pkg)
448 + self._dynamic_config._unsolvable_blockers.discard(pkg)
449 + self._dynamic_config._blocked_pkgs.discard(pkg)
450 + self._dynamic_config._blocked_world_pkgs.pop(pkg, None)
451 +
452 + for child in children:
453 + if not self._dynamic_config.digraph.parent_nodes(child):
454 + self._remove_pkg(child)
455 +
456 + # Clear caches.
457 + self._dynamic_config._filtered_trees[pkg.root]["porttree"].dbapi._clear_cache()
458 + self._dynamic_config._highest_pkg_cache.clear()
459 +
460 +
461 def _check_masks(self, pkg):
462
463 slot_key = (pkg.root, pkg.slot_atom)
464
465 diff --git a/pym/portage/tests/resolver/test_backtracking.py b/pym/portage/tests/resolver/test_backtracking.py
466 index 9dc37ac..3b69eda 100644
467 --- a/pym/portage/tests/resolver/test_backtracking.py
468 +++ b/pym/portage/tests/resolver/test_backtracking.py
469 @@ -1,4 +1,4 @@
470 -# Copyright 2010 Gentoo Foundation
471 +# Copyright 2010-2014 Gentoo Foundation
472 # Distributed under the terms of the GNU General Public License v2
473
474 from portage.tests import TestCase
475 @@ -31,7 +31,7 @@ class BacktrackingTestCase(TestCase):
476 playground.cleanup()
477
478
479 - def testHittingTheBacktrackLimit(self):
480 + def testBacktrackNotNeeded(self):
481 ebuilds = {
482 "dev-libs/A-1": {},
483 "dev-libs/A-2": {},
484 @@ -45,17 +45,10 @@ class BacktrackingTestCase(TestCase):
485 ResolverPlaygroundTestCase(
486 ["dev-libs/C", "dev-libs/D"],
487 all_permutations = True,
488 + options = { "--backtrack": 1 },
489 mergelist = ["dev-libs/A-1", "dev-libs/B-1", "dev-libs/C-1", "dev-libs/D-1"],
490 ignore_mergelist_order = True,
491 success = True),
492 - #This one hits the backtrack limit. Be aware that this depends on the argument order.
493 - ResolverPlaygroundTestCase(
494 - ["dev-libs/D", "dev-libs/C"],
495 - options = { "--backtrack": 1 },
496 - mergelist = ["dev-libs/A-1", "dev-libs/B-1", "dev-libs/A-2", "dev-libs/B-2", "dev-libs/C-1", "dev-libs/D-1"],
497 - ignore_mergelist_order = True,
498 - slot_collision_solutions = [],
499 - success = False),
500 )
501
502 playground = ResolverPlayground(ebuilds=ebuilds)
503
504 diff --git a/pym/portage/tests/resolver/test_blocker.py b/pym/portage/tests/resolver/test_blocker.py
505 new file mode 100644
506 index 0000000..94a88b8
507 --- /dev/null
508 +++ b/pym/portage/tests/resolver/test_blocker.py
509 @@ -0,0 +1,48 @@
510 +# Copyright 2014 Gentoo Foundation
511 +# Distributed under the terms of the GNU General Public License v2
512 +
513 +from portage.tests import TestCase
514 +from portage.tests.resolver.ResolverPlayground import ResolverPlayground, ResolverPlaygroundTestCase
515 +
516 +class SlotConflictWithBlockerTestCase(TestCase):
517 +
518 + def testBlocker(self):
519 + ebuilds = {
520 + "dev-libs/A-1": { "DEPEND": "dev-libs/X" },
521 + "dev-libs/B-1": { "DEPEND": "<dev-libs/X-2" },
522 + "dev-libs/C-1": { "DEPEND": "<dev-libs/X-3" },
523 +
524 + "dev-libs/X-1": { "EAPI": "2", "RDEPEND": "!=dev-libs/Y-1" },
525 + "dev-libs/X-2": { "EAPI": "2", "RDEPEND": "!=dev-libs/Y-2" },
526 + "dev-libs/X-3": { "EAPI": "2", "RDEPEND": "!=dev-libs/Y-3" },
527 +
528 + "dev-libs/Y-1": { "SLOT": "1" },
529 + "dev-libs/Y-2": { "SLOT": "2" },
530 + "dev-libs/Y-3": { "SLOT": "3" },
531 + }
532 +
533 + installed = {
534 + "dev-libs/Y-1": { "SLOT": "1" },
535 + "dev-libs/Y-2": { "SLOT": "2" },
536 + "dev-libs/Y-3": { "SLOT": "3" },
537 + }
538 +
539 + test_cases = (
540 + ResolverPlaygroundTestCase(
541 + ["dev-libs/A", "dev-libs/B", "dev-libs/C"],
542 + options = { "--backtrack": 0 },
543 + all_permutations = True,
544 + success = True,
545 + ambiguous_merge_order = True,
546 + mergelist = ["dev-libs/X-1", "[uninstall]dev-libs/Y-1", "!=dev-libs/Y-1", \
547 + ("dev-libs/A-1", "dev-libs/B-1", "dev-libs/C-1")]),
548 + )
549 +
550 + playground = ResolverPlayground(ebuilds=ebuilds,
551 + installed=installed, debug=False)
552 + try:
553 + for test_case in test_cases:
554 + playground.run_TestCase(test_case)
555 + self.assertEqual(test_case.test_success, True, test_case.fail_msg)
556 + finally:
557 + playground.cleanup()
558
559 diff --git a/pym/portage/tests/resolver/test_slot_collisions.py b/pym/portage/tests/resolver/test_slot_collisions.py
560 index 95d68fe..fdd6dd6 100644
561 --- a/pym/portage/tests/resolver/test_slot_collisions.py
562 +++ b/pym/portage/tests/resolver/test_slot_collisions.py
563 @@ -1,4 +1,4 @@
564 -# Copyright 2010-2011 Gentoo Foundation
565 +# Copyright 2010-2011,2014 Gentoo Foundation
566 # Distributed under the terms of the GNU General Public License v2
567
568 from portage.tests import TestCase
569 @@ -153,3 +153,76 @@ class SlotCollisionTestCase(TestCase):
570 self.assertEqual(test_case.test_success, True, test_case.fail_msg)
571 finally:
572 playground.cleanup()
573 +
574 + def testConnectedCollision(self):
575 + """
576 + Ensure that we are able to solve connected slot conflicts
577 + which cannot be solved each on their own.
578 + """
579 + ebuilds = {
580 + "dev-libs/A-1": { "RDEPEND": "=dev-libs/X-1" },
581 + "dev-libs/B-1": { "RDEPEND": "dev-libs/X" },
582 +
583 + "dev-libs/X-1": { "RDEPEND": "=dev-libs/Y-1" },
584 + "dev-libs/X-2": { "RDEPEND": "=dev-libs/Y-2" },
585 +
586 + "dev-libs/Y-1": { "PDEPEND": "=dev-libs/X-1" },
587 + "dev-libs/Y-2": { "PDEPEND": "=dev-libs/X-2" },
588 + }
589 +
590 + test_cases = (
591 + ResolverPlaygroundTestCase(
592 + ["dev-libs/A", "dev-libs/B"],
593 + all_permutations = True,
594 + options = { "--backtrack": 0 },
595 + success = True,
596 + ambiguous_merge_order = True,
597 + mergelist = ["dev-libs/Y-1", "dev-libs/X-1", ("dev-libs/A-1", "dev-libs/B-1")]),
598 + )
599 +
600 + playground = ResolverPlayground(ebuilds=ebuilds, debug=False)
601 + try:
602 + for test_case in test_cases:
603 + playground.run_TestCase(test_case)
604 + self.assertEqual(test_case.test_success, True, test_case.fail_msg)
605 + finally:
606 + playground.cleanup()
607 +
608 +
609 + def testDeeplyConnectedCollision(self):
610 + """
611 + Like testConnectedCollision, except that there is another
612 + level of dependencies between the two conflicts.
613 + """
614 + ebuilds = {
615 + "dev-libs/A-1": { "RDEPEND": "=dev-libs/X-1" },
616 + "dev-libs/B-1": { "RDEPEND": "dev-libs/X" },
617 +
618 + "dev-libs/X-1": { "RDEPEND": "dev-libs/K" },
619 + "dev-libs/X-2": { "RDEPEND": "dev-libs/L" },
620 +
621 + "dev-libs/K-1": { "RDEPEND": "=dev-libs/Y-1" },
622 + "dev-libs/L-1": { "RDEPEND": "=dev-libs/Y-2" },
623 +
624 + "dev-libs/Y-1": { "PDEPEND": "=dev-libs/X-1" },
625 + "dev-libs/Y-2": { "PDEPEND": "=dev-libs/X-2" },
626 + }
627 +
628 + test_cases = (
629 + ResolverPlaygroundTestCase(
630 + ["dev-libs/A", "dev-libs/B"],
631 + all_permutations = True,
632 + options = { "--backtrack": 0 },
633 + success = True,
634 + ignore_mergelist_order = True,
635 + mergelist = ["dev-libs/Y-1", "dev-libs/X-1", "dev-libs/K-1", \
636 + "dev-libs/A-1", "dev-libs/B-1"]),
637 + )
638 +
639 + playground = ResolverPlayground(ebuilds=ebuilds, debug=False)
640 + try:
641 + for test_case in test_cases:
642 + playground.run_TestCase(test_case)
643 + self.assertEqual(test_case.test_success, True, test_case.fail_msg)
644 + finally:
645 + playground.cleanup()