Gentoo Archives: gentoo-portage-dev

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