Gentoo Archives: gentoo-commits

From: Zac Medico <zmedico@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: lib/_emerge/, lib/portage/tests/resolver/
Date: Sat, 15 Feb 2020 00:05:49
Message-Id: 1581721837.079f8c4a36ccc2ef5e25e7a57cd0707640f82592.zmedico@gentoo
1 commit: 079f8c4a36ccc2ef5e25e7a57cd0707640f82592
2 Author: Zac Medico <zmedico <AT> gentoo <DOT> org>
3 AuthorDate: Fri Feb 14 19:21:28 2020 +0000
4 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org>
5 CommitDate: Fri Feb 14 23:10:37 2020 +0000
6 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=079f8c4a
7
8 depclean: ensure consistency with update actions (bug 649622)
9
10 Make depclean traverse dependencies in the same order as update
11 actions, in order to ensure consistency in decisions which are
12 dependent on the order of dependency evaluation due to inconsistent
13 use of || preferences in different packages.
14
15 In unit tests, update test_virtual_w3m_realistic to assert that
16 the order of graph traversal is deterministic and consistent
17 between update and removal (depclean) actions.
18
19 Bug: https://bugs.gentoo.org/649622
20 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>
21
22 lib/_emerge/actions.py | 33 +++++++---
23 lib/_emerge/depgraph.py | 21 +++++--
24 lib/portage/tests/resolver/ResolverPlayground.py | 77 +++++++++++++++---------
25 lib/portage/tests/resolver/test_or_choices.py | 12 +++-
26 4 files changed, 96 insertions(+), 47 deletions(-)
27
28 diff --git a/lib/_emerge/actions.py b/lib/_emerge/actions.py
29 index 31252af16..4bf9ce425 100644
30 --- a/lib/_emerge/actions.py
31 +++ b/lib/_emerge/actions.py
32 @@ -1,8 +1,9 @@
33 -# Copyright 1999-2019 Gentoo Authors
34 +# Copyright 1999-2020 Gentoo Authors
35 # Distributed under the terms of the GNU General Public License v2
36
37 from __future__ import division, print_function, unicode_literals
38
39 +import collections
40 import errno
41 import logging
42 import operator
43 @@ -741,7 +742,19 @@ def action_depclean(settings, trees, ldpath_mtimes,
44
45 return rval
46
47 +
48 def calc_depclean(settings, trees, ldpath_mtimes,
49 + myopts, action, args_set, spinner):
50 + result = _calc_depclean(settings, trees, ldpath_mtimes,
51 + myopts, action, args_set, spinner)
52 + return result.returncode, result.cleanlist, result.ordered, result.req_pkg_count
53 +
54 +
55 +_depclean_result = collections.namedtuple('_depclean_result',
56 + ('returncode', 'cleanlist', 'ordered', 'req_pkg_count', 'depgraph'))
57 +
58 +
59 +def _calc_depclean(settings, trees, ldpath_mtimes,
60 myopts, action, args_set, spinner):
61 allow_missing_deps = bool(args_set)
62
63 @@ -805,7 +818,7 @@ def calc_depclean(settings, trees, ldpath_mtimes,
64 writemsg_level(_("!!! Aborting due to set configuration "
65 "errors displayed above.\n"),
66 level=logging.ERROR, noiselevel=-1)
67 - return 1, [], False, 0
68 + return _depclean_result(1, [], False, 0, None)
69
70 if action == "depclean":
71 emergelog(xterm_titles, " >>> depclean")
72 @@ -920,7 +933,7 @@ def calc_depclean(settings, trees, ldpath_mtimes,
73 resolver.display_problems()
74
75 if not success:
76 - return 1, [], False, 0
77 + return _depclean_result(1, [], False, 0, resolver)
78
79 def unresolved_deps():
80
81 @@ -1020,7 +1033,7 @@ def calc_depclean(settings, trees, ldpath_mtimes,
82 return False
83
84 if unresolved_deps():
85 - return 1, [], False, 0
86 + return _depclean_result(1, [], False, 0, resolver)
87
88 graph = resolver._dynamic_config.digraph.copy()
89 required_pkgs_total = 0
90 @@ -1321,7 +1334,7 @@ def calc_depclean(settings, trees, ldpath_mtimes,
91 runtime_slot_op=True),
92 root=pkg.root)):
93 resolver.display_problems()
94 - return 1, [], False, 0
95 + return _depclean_result(1, [], False, 0, resolver)
96
97 writemsg_level("\nCalculating dependencies ")
98 success = resolver._complete_graph(
99 @@ -1329,9 +1342,9 @@ def calc_depclean(settings, trees, ldpath_mtimes,
100 writemsg_level("\b\b... done!\n")
101 resolver.display_problems()
102 if not success:
103 - return 1, [], False, 0
104 + return _depclean_result(1, [], False, 0, resolver)
105 if unresolved_deps():
106 - return 1, [], False, 0
107 + return _depclean_result(1, [], False, 0, resolver)
108
109 graph = resolver._dynamic_config.digraph.copy()
110 required_pkgs_total = 0
111 @@ -1340,7 +1353,7 @@ def calc_depclean(settings, trees, ldpath_mtimes,
112 required_pkgs_total += 1
113 cleanlist = create_cleanlist()
114 if not cleanlist:
115 - return 0, [], False, required_pkgs_total
116 + return _depclean_result(0, [], False, required_pkgs_total, resolver)
117 clean_set = set(cleanlist)
118
119 if clean_set:
120 @@ -1458,8 +1471,8 @@ def calc_depclean(settings, trees, ldpath_mtimes,
121 graph.remove(node)
122 cleanlist.append(node.cpv)
123
124 - return 0, cleanlist, ordered, required_pkgs_total
125 - return 0, [], False, required_pkgs_total
126 + return _depclean_result(0, cleanlist, ordered, required_pkgs_total, resolver)
127 + return _depclean_result(0, [], False, required_pkgs_total, resolver)
128
129 def action_deselect(settings, trees, opts, atoms):
130 enter_invalid = '--ask-enter-invalid' in opts
131
132 diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
133 index 8e0d79e29..27696ad40 100644
134 --- a/lib/_emerge/depgraph.py
135 +++ b/lib/_emerge/depgraph.py
136 @@ -6955,9 +6955,18 @@ class depgraph(object):
137 # Removal actions may override sets with temporary
138 # replacements that have had atoms removed in order
139 # to implement --deselect behavior.
140 - required_set_names = set(required_sets[root])
141 depgraph_sets.sets.clear()
142 depgraph_sets.sets.update(required_sets[root])
143 + if 'world' in depgraph_sets.sets:
144 + # For consistent order of traversal for both update
145 + # and removal (depclean) actions, sets other that
146 + # world are always nested under the world set.
147 + world_atoms = list(depgraph_sets.sets['world'])
148 + world_atoms.extend(SETPREFIX + s for s in required_sets[root] if s != 'world')
149 + depgraph_sets.sets['world'] = InternalPackageSet(initial_atoms=world_atoms)
150 + required_set_names = {'world'}
151 + else:
152 + required_set_names = set(required_sets[root])
153 if "remove" not in self._dynamic_config.myparams and \
154 root == self._frozen_config.target_root and \
155 already_deep:
156 @@ -6967,7 +6976,7 @@ class depgraph(object):
157 not self._dynamic_config._dep_stack:
158 continue
159 root_config = self._frozen_config.roots[root]
160 - for s in required_set_names:
161 + for s in sorted(required_set_names):
162 pset = depgraph_sets.sets.get(s)
163 if pset is None:
164 pset = root_config.sets[s]
165 @@ -6977,10 +6986,10 @@ class depgraph(object):
166
167 self._set_args(args)
168 for arg in self._expand_set_args(args, add_to_digraph=True):
169 - for atom in sorted(arg.pset.getAtoms(), reverse=True):
170 - self._dynamic_config._dep_stack.append(
171 - Dependency(atom=atom, root=arg.root_config.root,
172 - parent=arg, depth=self._UNREACHABLE_DEPTH))
173 + for atom in sorted(arg.pset.getAtoms()):
174 + if not self._add_dep(Dependency(atom=atom, root=arg.root_config.root,
175 + parent=arg, depth=self._UNREACHABLE_DEPTH), allow_unsatisfied=True):
176 + return 0
177
178 if True:
179 if self._dynamic_config._ignored_deps:
180
181 diff --git a/lib/portage/tests/resolver/ResolverPlayground.py b/lib/portage/tests/resolver/ResolverPlayground.py
182 index cc0aa46e9..0456ce2e2 100644
183 --- a/lib/portage/tests/resolver/ResolverPlayground.py
184 +++ b/lib/portage/tests/resolver/ResolverPlayground.py
185 @@ -1,4 +1,4 @@
186 -# Copyright 2010-2019 Gentoo Authors
187 +# Copyright 2010-2020 Gentoo Authors
188 # Distributed under the terms of the GNU General Public License v2
189
190 import bz2
191 @@ -22,9 +22,10 @@ from portage.util import ensure_dirs, normalize_path
192 from portage.versions import catsplit
193
194 import _emerge
195 -from _emerge.actions import calc_depclean
196 +from _emerge.actions import _calc_depclean
197 from _emerge.Blocker import Blocker
198 from _emerge.create_depgraph_params import create_depgraph_params
199 +from _emerge.DependencyArg import DependencyArg
200 from _emerge.depgraph import backtrack_depgraph
201 from _emerge.RootConfig import RootConfig
202
203 @@ -593,11 +594,16 @@ class ResolverPlayground(object):
204 _emerge.emergelog._disable = True
205
206 if action in ("depclean", "prune"):
207 - rval, cleanlist, ordered, req_pkg_count = \
208 - calc_depclean(self.settings, self.trees, None,
209 + depclean_result = _calc_depclean(self.settings, self.trees, None,
210 options, action, InternalPackageSet(initial_atoms=atoms, allow_wildcard=True), None)
211 result = ResolverPlaygroundDepcleanResult(
212 - atoms, rval, cleanlist, ordered, req_pkg_count)
213 + atoms,
214 + depclean_result.returncode,
215 + depclean_result.cleanlist,
216 + depclean_result.ordered,
217 + depclean_result.req_pkg_count,
218 + depclean_result.depgraph,
219 + )
220 else:
221 params = create_depgraph_params(options, action)
222 success, depgraph, favorites = backtrack_depgraph(
223 @@ -780,18 +786,46 @@ class ResolverPlaygroundTestCase(object):
224 return False
225 return True
226
227 +
228 +def _mergelist_str(x, depgraph):
229 + if isinstance(x, DependencyArg):
230 + mergelist_str = x.arg
231 + elif isinstance(x, Blocker):
232 + mergelist_str = x.atom
233 + else:
234 + repo_str = ""
235 + if x.repo != "test_repo":
236 + repo_str = _repo_separator + x.repo
237 + build_id_str = ""
238 + if (x.type_name == "binary" and
239 + x.cpv.build_id is not None):
240 + build_id_str = "-%s" % x.cpv.build_id
241 + mergelist_str = x.cpv + build_id_str + repo_str
242 + if x.built:
243 + if x.operation == "merge":
244 + desc = x.type_name
245 + else:
246 + desc = x.operation
247 + mergelist_str = "[%s]%s" % (desc, mergelist_str)
248 + if x.root != depgraph._frozen_config._running_root.root:
249 + mergelist_str += "{targetroot}"
250 + return mergelist_str
251 +
252 +
253 class ResolverPlaygroundResult(object):
254
255 checks = (
256 "success", "mergelist", "use_changes", "license_changes",
257 "unstable_keywords", "slot_collision_solutions",
258 "circular_dependency_solutions", "needed_p_mask_changes",
259 - "unsatisfied_deps", "forced_rebuilds", "required_use_unsatisfied"
260 + "unsatisfied_deps", "forced_rebuilds", "required_use_unsatisfied",
261 + "graph_order",
262 )
263 optional_checks = (
264 "forced_rebuilds",
265 "required_use_unsatisfied",
266 - "unsatisfied_deps"
267 + "unsatisfied_deps",
268 + "graph_order",
269 )
270
271 def __init__(self, atoms, success, mydepgraph, favorites):
272 @@ -810,30 +844,12 @@ class ResolverPlaygroundResult(object):
273 self.forced_rebuilds = None
274 self.required_use_unsatisfied = None
275
276 + self.graph_order = [_mergelist_str(node, self.depgraph) for node in self.depgraph._dynamic_config.digraph]
277 +
278 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
279 self.mergelist = []
280 - host_root = self.depgraph._frozen_config._running_root.root
281 for x in self.depgraph._dynamic_config._serialized_tasks_cache:
282 - if isinstance(x, Blocker):
283 - self.mergelist.append(x.atom)
284 - else:
285 - repo_str = ""
286 - if x.repo != "test_repo":
287 - repo_str = _repo_separator + x.repo
288 - build_id_str = ""
289 - if (x.type_name == "binary" and
290 - x.cpv.build_id is not None):
291 - build_id_str = "-%s" % x.cpv.build_id
292 - mergelist_str = x.cpv + build_id_str + repo_str
293 - if x.built:
294 - if x.operation == "merge":
295 - desc = x.type_name
296 - else:
297 - desc = x.operation
298 - mergelist_str = "[%s]%s" % (desc, mergelist_str)
299 - if x.root != host_root:
300 - mergelist_str += "{targetroot}"
301 - self.mergelist.append(mergelist_str)
302 + self.mergelist.append(_mergelist_str(x, self.depgraph))
303
304 if self.depgraph._dynamic_config._needed_use_config_changes:
305 self.use_changes = {}
306 @@ -894,14 +910,17 @@ class ResolverPlaygroundDepcleanResult(object):
307
308 checks = (
309 "success", "cleanlist", "ordered", "req_pkg_count",
310 + "graph_order",
311 )
312 optional_checks = (
313 "ordered", "req_pkg_count",
314 + "graph_order",
315 )
316
317 - def __init__(self, atoms, rval, cleanlist, ordered, req_pkg_count):
318 + def __init__(self, atoms, rval, cleanlist, ordered, req_pkg_count, depgraph):
319 self.atoms = atoms
320 self.success = rval == 0
321 self.cleanlist = cleanlist
322 self.ordered = ordered
323 self.req_pkg_count = req_pkg_count
324 + self.graph_order = [_mergelist_str(node, depgraph) for node in depgraph._dynamic_config.digraph]
325
326 diff --git a/lib/portage/tests/resolver/test_or_choices.py b/lib/portage/tests/resolver/test_or_choices.py
327 index f31a5ff22..5c6803784 100644
328 --- a/lib/portage/tests/resolver/test_or_choices.py
329 +++ b/lib/portage/tests/resolver/test_or_choices.py
330 @@ -667,12 +667,16 @@ class OrChoicesTestCase(TestCase):
331
332 # Test for bug 649622 (with www-client/w3m installed via
333 # xorg-server dependency), where virtual/w3m was pulled in
334 - # only to be removed by the next emerge --depclean.
335 + # only to be removed by the next emerge --depclean. Note
336 + # that graph_order must be deterministic in order to achieve
337 + # deterministic results which are consistent between both
338 + # update and removal (depclean) actions.
339 ResolverPlaygroundTestCase(
340 ['@world'],
341 options = {'--update': True, '--deep': True},
342 success = True,
343 mergelist=['virtual/w3m-0'],
344 + graph_order=['@world', '@system', '@selected', '@profile', '[nomerge]app-misc/neofetch-6.1.0', '[nomerge]mail-client/neomutt-20191207', '[nomerge]www-client/lynx-2.9.0_pre4', '[nomerge]x11-base/xorg-server-1.20.7', '[nomerge]app-text/xmlto-0.0.28-r1', '[nomerge]www-client/w3m-0.5.3_p20190105', 'virtual/w3m-0'],
345 ),
346
347 )
348 @@ -702,12 +706,16 @@ class OrChoicesTestCase(TestCase):
349 # Test for bug 649622, where virtual/w3m is removed by
350 # emerge --depclean immediately after it's installed
351 # by a world update. Since virtual/w3m-0 is not removed
352 - # here, this case fails to reproduce bug 649622.
353 + # here, this case fails to reproduce bug 649622. Note
354 + # that graph_order must be deterministic in order to achieve
355 + # deterministic results which are consistent between both
356 + # update and removal (depclean) actions.
357 ResolverPlaygroundTestCase(
358 [],
359 options={'--depclean': True},
360 success=True,
361 cleanlist=[],
362 + graph_order=['@world', '@system', '@selected', '@profile', '@____depclean_protected_set____', '[nomerge]app-misc/neofetch-6.1.0', '[nomerge]mail-client/neomutt-20191207', '[nomerge]www-client/lynx-2.9.0_pre4', '[nomerge]x11-base/xorg-server-1.20.7', '[nomerge]app-text/xmlto-0.0.28-r1', '[nomerge]www-client/w3m-0.5.3_p20190105', '[nomerge]virtual/w3m-0'],
363 ),
364
365 )