Tests: resolver/test_slot_collisions: Check for slot_collision_solutions
[portage.git] / pym / portage / tests / resolver / ResolverPlayground.py
1 # Copyright 2010 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 from itertools import chain, permutations
5 import shutil
6 import tempfile
7 import portage
8 from portage import os
9 from portage.dbapi.vartree import vartree
10 from portage.dbapi.porttree import portagetree
11 from portage.dbapi.bintree import binarytree
12 from portage.dep import Atom
13 from portage.package.ebuild.config import config
14 from portage.sets import SetConfig
15 from portage.versions import catsplit
16
17 from _emerge.Blocker import Blocker
18 from _emerge.create_depgraph_params import create_depgraph_params
19 from _emerge.depgraph import backtrack_depgraph
20 from _emerge.RootConfig import RootConfig
21 from _emerge.main import setconfig_fallback
22
23 class ResolverPlayground(object):
24         """
25         This class help to create the necessary files on disk and
26         the needed settings instances, etc. for the resolver to do
27         it's work.
28         """
29         
30         def __init__(self, ebuilds={}, installed={}, profile={}):
31                 """
32                 ebuilds: cpv -> metadata mapping simulating avaiable ebuilds. 
33                 installed: cpv -> metadata mapping simulating installed packages.
34                         If a metadata key is missing, it gets a default value.
35                 profile: settings defined by the profile.
36                 """
37                 self.root = tempfile.mkdtemp() + os.path.sep
38                 self.portdir = os.path.join(self.root, "usr/portage")
39                 self.vdbdir = os.path.join(self.root, "var/db/pkg")
40                 os.makedirs(self.portdir)
41                 os.makedirs(self.vdbdir)
42                 
43                 self._create_ebuilds(ebuilds)
44                 self._create_installed(installed)
45                 self._create_profile(ebuilds, installed, profile)
46                 
47                 self.settings, self.trees = self._load_config()
48                 
49                 self._create_ebuild_manifests(ebuilds)
50
51         def _create_ebuilds(self, ebuilds):
52                 for cpv in ebuilds:
53                         a = Atom("=" + cpv)
54                         ebuild_dir = os.path.join(self.portdir, a.cp)
55                         ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
56                         try:
57                                 os.makedirs(ebuild_dir)
58                         except os.error:
59                                 pass
60                         
61                         metadata = ebuilds[cpv]
62                         eapi = metadata.get("EAPI", 0)
63                         slot = metadata.get("SLOT", 0)
64                         keywords = metadata.get("KEYWORDS", "x86")
65                         iuse = metadata.get("IUSE", "")
66                         depend = metadata.get("DEPEND", "")
67                         rdepend = metadata.get("RDEPEND", None)
68                         pdepend = metadata.get("PDEPEND", None)
69                         required_use = metadata.get("REQUIRED_USE", None)
70
71                         f = open(ebuild_path, "w")
72                         f.write('EAPI="' + str(eapi) + '"\n')
73                         f.write('SLOT="' + str(slot) + '"\n')
74                         f.write('KEYWORDS="' + str(keywords) + '"\n')
75                         f.write('IUSE="' + str(iuse) + '"\n')
76                         f.write('DEPEND="' + str(depend) + '"\n')
77                         if rdepend is not None:
78                                 f.write('RDEPEND="' + str(rdepend) + '"\n')
79                         if rdepend is not None:
80                                 f.write('PDEPEND="' + str(pdepend) + '"\n')
81                         if required_use is not None:
82                                 f.write('REQUIRED_USE="' + str(required_use) + '"\n')
83                         f.close()
84
85         def _create_ebuild_manifests(self, ebuilds):
86                 for cpv in ebuilds:
87                         a = Atom("=" + cpv)
88                         ebuild_dir = os.path.join(self.portdir, a.cp)
89                         ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
90                         
91                         portage.util.noiselimit = -1
92                         tmpsettings = config(clone=self.settings)
93                         portdb = self.trees[self.root]["porttree"].dbapi
94                         portage.doebuild(ebuild_path, "digest", self.root, tmpsettings,
95                                 tree="porttree", mydbapi=portdb)
96                         portage.util.noiselimit = 0
97                 
98         def _create_installed(self, installed):
99                 for cpv in installed:
100                         a = Atom("=" + cpv)
101                         vdb_pkg_dir = os.path.join(self.vdbdir, a.cpv)
102                         try:
103                                 os.makedirs(vdb_pkg_dir)
104                         except os.error:
105                                 pass
106
107                         metadata = installed[cpv]
108                         eapi = metadata.get("EAPI", 0)
109                         slot = metadata.get("SLOT", 0)
110                         keywords = metadata.get("KEYWORDS", "~x86")
111                         iuse = metadata.get("IUSE", "")
112                         use = metadata.get("USE", "")
113                         depend = metadata.get("DEPEND", "")
114                         rdepend = metadata.get("RDEPEND", None)
115                         pdepend = metadata.get("PDEPEND", None)
116                         required_use = metadata.get("REQUIRED_USE", None)
117                         
118                         def write_key(key, value):
119                                 f = open(os.path.join(vdb_pkg_dir, key), "w")
120                                 f.write(str(value) + "\n")
121                                 f.close()
122                         
123                         write_key("EAPI", eapi)
124                         write_key("SLOT", slot)
125                         write_key("KEYWORDS", keywords)
126                         write_key("IUSE", iuse)
127                         write_key("USE", use)
128                         write_key("DEPEND", depend)
129                         if rdepend is not None:
130                                 write_key("RDEPEND", rdepend)
131                         if rdepend is not None:
132                                 write_key("PDEPEND", pdepend)
133                         if required_use is not None:
134                                 write_key("REQUIRED_USE", required_use)
135
136         def _create_profile(self, ebuilds, installed, profile):
137                 #Create $PORTDIR/profiles/categories
138                 categories = set()
139                 for cpv in chain(ebuilds.keys(), installed.keys()):
140                         categories.add(catsplit(cpv)[0])
141                 
142                 profile_dir = os.path.join(self.portdir, "profiles")
143                 try:
144                         os.makedirs(profile_dir)
145                 except os.error:
146                         pass
147                 
148                 categories_file = os.path.join(profile_dir, "categories")
149                 
150                 f = open(categories_file, "w")
151                 for cat in categories:
152                         f.write(cat + "\n")
153                 f.close()
154                 
155                 #Create $PORTDIR/eclass (we fail to digest the ebuilds if it's not there)
156                 os.makedirs(os.path.join(self.portdir, "eclass"))
157                 
158                 if profile:
159                         #This is meant to allow the consumer to set up his own profile,
160                         #with package.mask and what not.
161                         raise NotImplentedError()
162
163         def _load_config(self):
164                 env = {
165                         "ACCEPT_KEYWORDS": "x86",
166                         "PORTDIR": self.portdir,
167                         "ROOT": self.root,
168                         'PORTAGE_TMPDIR' : os.path.join(self.root, 'var/tmp')
169                 }
170
171                 settings = config(config_root=self.root, target_root=self.root, env=env)
172                 settings.lock()
173
174                 trees = {
175                         self.root: {
176                                         "virtuals": settings.getvirtuals(),
177                                         "vartree": vartree(self.root, categories=settings.categories, settings=settings),
178                                         "porttree": portagetree(self.root, settings=settings),
179                                         "bintree": binarytree(self.root, os.path.join(self.root, "usr/portage/packages"), settings=settings)
180                                 }
181                         }
182
183                 for root, root_trees in trees.items():
184                         settings = root_trees["vartree"].settings
185                         settings._init_dirs()
186                         setconfig = SetConfig([], settings, root_trees)
187                         root_trees["root_config"] = RootConfig(settings, root_trees, setconfig)
188                         setconfig_fallback(root_trees["root_config"])
189                 
190                 return settings, trees
191
192         def run(self, atoms, options={}, action=None):
193                 options = options.copy()
194                 options["--pretend"] = True
195                 options["--quiet"] = True
196                 options["--root"] = self.root
197                 options["--config-root"] = self.root
198                 options["--root-deps"] = "rdeps"
199                 # Add a fake _test_ option that can be used for
200                 # conditional test code.
201                 options["_test_"] = True
202
203                 portage.util.noiselimit = -2
204                 params = create_depgraph_params(options, action)
205                 success, depgraph, favorites = backtrack_depgraph(
206                         self.settings, self.trees, options, params, action, atoms, None)
207                 depgraph.display_problems()
208                 result = ResolverPlaygroundResult(atoms, success, depgraph, favorites)
209                 portage.util.noiselimit = 0
210
211                 return result
212
213         def run_TestCase(self, test_case):
214                 if not isinstance(test_case, ResolverPlaygroundTestCase):
215                         raise TypeError("ResolverPlayground needs a ResolverPlaygroundTestCase")
216                 for atoms in test_case.requests:
217                         result = self.run(atoms, test_case.options, test_case.action)
218                         if not test_case.compare_with_result(result):
219                                 return
220
221         def cleanup(self):
222                 shutil.rmtree(self.root)
223
224 class ResolverPlaygroundTestCase(object):
225
226         def __init__(self, request, **kwargs):
227                 self.checks = {
228                         "success": None,
229                         "mergelist": None,
230                         "use_changes": None,
231                         "unstable_keywords": None,
232                         "slot_collision_solutions": None,
233                         }
234                 
235                 self.all_permutations = kwargs.pop("all_permutations", False)
236                 self.ignore_mergelist_order = kwargs.pop("ignore_mergelist_order", False)
237
238                 if self.all_permutations:
239                         self.requests = list(permutations(request))
240                 else:
241                         self.requests = [request]
242
243                 self.options = kwargs.pop("options", {})
244                 self.action = kwargs.pop("action", None)
245                 self.test_success = True
246                 self.fail_msg = None
247                 
248                 for key, value in kwargs.items():
249                         if not key in self.checks:
250                                 raise KeyError("Not an avaiable check: '%s'" % key)
251                         self.checks[key] = value
252         
253         def compare_with_result(self, result):
254                 fail_msgs = []
255                 for key, value in self.checks.items():
256                         got = getattr(result, key)
257                         expected = value
258                         if key == "mergelist" and self.ignore_mergelist_order and value is not None :
259                                 got = set(got)
260                                 expected = set(expected)
261                         elif key == "unstable_keywords" and expected is not None:
262                                 expected = set(expected)
263
264                         if got != expected:
265                                 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
266                                         key + ", expected: " + str(expected) + ", got: " + str(got))
267                 if fail_msgs:
268                         self.test_success = False
269                         self.fail_msg = "\n".join(fail_msgs)
270                         return False
271                 return True
272
273 class ResolverPlaygroundResult(object):
274         def __init__(self, atoms, success, mydepgraph, favorites):
275                 self.atoms = atoms
276                 self.success = success
277                 self.depgraph = mydepgraph
278                 self.favorites = favorites
279                 self.mergelist = None
280                 self.use_changes = None
281                 self.unstable_keywords = None
282                 self.slot_collision_solutions = None
283
284                 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
285                         self.mergelist = []
286                         for x in self.depgraph._dynamic_config._serialized_tasks_cache:
287                                 if isinstance(x, Blocker):
288                                         self.mergelist.append(x.atom)
289                                 else:
290                                         self.mergelist.append(x.cpv)
291
292                 if self.depgraph._dynamic_config._needed_use_config_changes:
293                         self.use_changes = {}
294                         for pkg, needed_use_config_changes in \
295                                 self.depgraph._dynamic_config._needed_use_config_changes.items():
296                                 new_use, changes = needed_use_config_changes
297                                 self.use_changes[pkg.cpv] = changes
298
299                 if self.depgraph._dynamic_config._needed_unstable_keywords:
300                         self.unstable_keywords = set()
301                         for pkg in self.depgraph._dynamic_config._needed_unstable_keywords:
302                                 self.unstable_keywords.add(pkg.cpv)
303
304                 if self.depgraph._dynamic_config._slot_conflict_handler is not None:
305                         self.slot_collision_solutions  = []
306                         handler = self.depgraph._dynamic_config._slot_conflict_handler
307
308                         for solution in handler.solutions:
309                                 s = {}
310                                 for pkg in solution:
311                                         changes = {}
312                                         for flag, state in solution[pkg].items():
313                                                 if state == "enabled":
314                                                         changes[flag] = True
315                                                 else:
316                                                         changes[flag] = False
317                                         s[pkg.cpv] = changes
318                                 self.slot_collision_solutions.append(s)