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() |