Fix ResolverPlayground.cleanup() to cleanup the portdbapi instance.
[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.const import PORTAGE_BASE_PATH
10 from portage.dbapi.vartree import vartree
11 from portage.dbapi.porttree import portagetree
12 from portage.dbapi.bintree import binarytree
13 from portage.dep import Atom
14 from portage.package.ebuild.config import config
15 from portage._sets import load_default_config
16 from portage.versions import catsplit
17
18 from _emerge.Blocker import Blocker
19 from _emerge.create_depgraph_params import create_depgraph_params
20 from _emerge.depgraph import backtrack_depgraph
21 from _emerge.RootConfig import RootConfig
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         config_files = frozenset(("package.use", "package.mask", "package.keywords", \
31                 "package.unmask", "package.properties", "package.license"))
32
33         def __init__(self, ebuilds={}, installed={}, profile={}, user_config={}, sets={}, world=[], debug=False):
34                 """
35                 ebuilds: cpv -> metadata mapping simulating avaiable ebuilds. 
36                 installed: cpv -> metadata mapping simulating installed packages.
37                         If a metadata key is missing, it gets a default value.
38                 profile: settings defined by the profile.
39                 """
40                 self.debug = debug
41                 self.root = "/"
42                 self.eprefix = tempfile.mkdtemp()
43                 self.eroot = self.root + self.eprefix.lstrip(os.sep) + os.sep
44                 self.portdir = os.path.join(self.eroot, "usr/portage")
45                 self.vdbdir = os.path.join(self.eroot, "var/db/pkg")
46                 os.makedirs(self.portdir)
47                 os.makedirs(self.vdbdir)
48
49                 if not debug:
50                         portage.util.noiselimit = -2
51
52                 self._create_ebuilds(ebuilds)
53                 self._create_installed(installed)
54                 self._create_profile(ebuilds, installed, profile, user_config, sets)
55                 self._create_world(world)
56
57                 self.settings, self.trees = self._load_config()
58
59                 self._create_ebuild_manifests(ebuilds)
60                 
61                 portage.util.noiselimit = 0
62
63         def _create_ebuilds(self, ebuilds):
64                 for cpv in ebuilds:
65                         a = Atom("=" + cpv)
66                         ebuild_dir = os.path.join(self.portdir, a.cp)
67                         ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
68                         try:
69                                 os.makedirs(ebuild_dir)
70                         except os.error:
71                                 pass
72                         
73                         metadata = ebuilds[cpv].copy()
74                         eapi = metadata.pop("EAPI", 0)
75                         lic = metadata.pop("LICENSE", 0)
76                         properties = metadata.pop("PROPERTIES", "")
77                         slot = metadata.pop("SLOT", 0)
78                         keywords = metadata.pop("KEYWORDS", "x86")
79                         iuse = metadata.pop("IUSE", "")
80                         depend = metadata.pop("DEPEND", "")
81                         rdepend = metadata.pop("RDEPEND", None)
82                         pdepend = metadata.pop("PDEPEND", None)
83                         required_use = metadata.pop("REQUIRED_USE", None)
84
85                         if metadata:
86                                 raise ValueError("metadata of ebuild '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
87
88                         f = open(ebuild_path, "w")
89                         f.write('EAPI="' + str(eapi) + '"\n')
90                         f.write('LICENSE="' + str(lic) + '"\n')
91                         f.write('PROPERTIES="' + str(properties) + '"\n')
92                         f.write('SLOT="' + str(slot) + '"\n')
93                         f.write('KEYWORDS="' + str(keywords) + '"\n')
94                         f.write('IUSE="' + str(iuse) + '"\n')
95                         f.write('DEPEND="' + str(depend) + '"\n')
96                         if rdepend is not None:
97                                 f.write('RDEPEND="' + str(rdepend) + '"\n')
98                         if pdepend is not None:
99                                 f.write('PDEPEND="' + str(pdepend) + '"\n')
100                         if required_use is not None:
101                                 f.write('REQUIRED_USE="' + str(required_use) + '"\n')
102                         f.close()
103
104         def _create_ebuild_manifests(self, ebuilds):
105                 for cpv in ebuilds:
106                         a = Atom("=" + cpv)
107                         ebuild_dir = os.path.join(self.portdir, a.cp)
108                         ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
109
110                         portage.util.noiselimit = -1
111                         tmpsettings = config(clone=self.settings)
112                         portdb = self.trees[self.root]["porttree"].dbapi
113                         portage.doebuild(ebuild_path, "digest", self.root, tmpsettings,
114                                 tree="porttree", mydbapi=portdb)
115                         portage.util.noiselimit = 0
116
117         def _create_installed(self, installed):
118                 for cpv in installed:
119                         a = Atom("=" + cpv)
120                         vdb_pkg_dir = os.path.join(self.vdbdir, a.cpv)
121                         try:
122                                 os.makedirs(vdb_pkg_dir)
123                         except os.error:
124                                 pass
125
126                         metadata = installed[cpv].copy()
127                         eapi = metadata.pop("EAPI", 0)
128                         lic = metadata.pop("LICENSE", "")
129                         properties = metadata.pop("PROPERTIES", "")
130                         slot = metadata.pop("SLOT", 0)
131                         keywords = metadata.pop("KEYWORDS", "~x86")
132                         iuse = metadata.pop("IUSE", "")
133                         use = metadata.pop("USE", "")
134                         depend = metadata.pop("DEPEND", "")
135                         rdepend = metadata.pop("RDEPEND", None)
136                         pdepend = metadata.pop("PDEPEND", None)
137                         required_use = metadata.pop("REQUIRED_USE", None)
138
139                         if metadata:
140                                 raise ValueError("metadata of installed '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
141
142                         def write_key(key, value):
143                                 f = open(os.path.join(vdb_pkg_dir, key), "w")
144                                 f.write(str(value) + "\n")
145                                 f.close()
146                         
147                         write_key("EAPI", eapi)
148                         write_key("LICENSE", lic)
149                         write_key("PROPERTIES", properties)
150                         write_key("SLOT", slot)
151                         write_key("KEYWORDS", keywords)
152                         write_key("IUSE", iuse)
153                         write_key("USE", use)
154                         write_key("DEPEND", depend)
155                         if rdepend is not None:
156                                 write_key("RDEPEND", rdepend)
157                         if pdepend is not None:
158                                 write_key("PDEPEND", pdepend)
159                         if required_use is not None:
160                                 write_key("REQUIRED_USE", required_use)
161
162         def _create_profile(self, ebuilds, installed, profile, user_config, sets):
163                 #Create $PORTDIR/profiles/categories
164                 categories = set()
165                 for cpv in chain(ebuilds.keys(), installed.keys()):
166                         categories.add(catsplit(cpv)[0])
167                 
168                 profile_dir = os.path.join(self.portdir, "profiles")
169                 try:
170                         os.makedirs(profile_dir)
171                 except os.error:
172                         pass
173                 
174                 categories_file = os.path.join(profile_dir, "categories")
175                 
176                 f = open(categories_file, "w")
177                 for cat in categories:
178                         f.write(cat + "\n")
179                 f.close()
180                 
181                 
182                 #Create $REPO/profiles/license_groups
183                 license_file = os.path.join(profile_dir, "license_groups")
184                 f = open(license_file, "w")
185                 f.write("EULA TEST\n")
186                 f.close()
187
188                 #Create $profile_dir/eclass (we fail to digest the ebuilds if it's not there)
189                 os.makedirs(os.path.join(self.portdir, "eclass"))
190
191                 sub_profile_dir = os.path.join(profile_dir, "default", "linux", "x86", "test_profile")
192                 os.makedirs(sub_profile_dir)
193                 
194                 eapi_file = os.path.join(sub_profile_dir, "eapi")
195                 f = open(eapi_file, "w")
196                 f.write("0\n")
197                 f.close()
198                 
199                 make_defaults_file = os.path.join(sub_profile_dir, "make.defaults")
200                 f = open(make_defaults_file, "w")
201                 f.write("ARCH=\"x86\"\n")
202                 f.write("ACCEPT_KEYWORDS=\"x86\"\n")
203                 f.close()
204                 
205                 use_force_file = os.path.join(sub_profile_dir, "use.force")
206                 f = open(use_force_file, "w")
207                 f.write("x86\n")
208                 f.close()
209
210                 if profile:
211                         #This is meant to allow the consumer to set up his own profile,
212                         #with package.mask and what not.
213                         raise NotImplementedError()
214
215                 #Create profile symlink
216                 os.makedirs(os.path.join(self.eroot, "etc"))
217                 os.symlink(sub_profile_dir, os.path.join(self.eroot, "etc", "make.profile"))
218
219                 user_config_dir = os.path.join(self.eroot, "etc", "portage")
220
221                 try:
222                         os.makedirs(user_config_dir)
223                 except os.error:
224                         pass
225
226                 for config_file, lines in user_config.items():
227                         if config_file not in self.config_files:
228                                 raise ValueError("Unknown config file: '%s'" % config_file)
229
230                         file_name = os.path.join(user_config_dir, config_file)
231                         f = open(file_name, "w")
232                         for line in lines:
233                                 f.write("%s\n" % line)
234                         f.close()
235
236                 #Create /usr/share/portage/config/sets/portage.conf
237                 default_sets_conf_dir = os.path.join(self.eroot, "usr/share/portage/config/sets")
238
239                 try:
240                         os.makedirs(default_sets_conf_dir)
241                 except os.error:
242                         pass
243
244                 provided_sets_portage_conf = \
245                         os.path.join(PORTAGE_BASE_PATH, "cnf/sets/portage.conf")
246                 os.symlink(provided_sets_portage_conf, os.path.join(default_sets_conf_dir, "portage.conf"))
247
248                 set_config_dir = os.path.join(user_config_dir, "sets")
249
250                 try:
251                         os.makedirs(set_config_dir)
252                 except os.error:
253                         pass
254
255                 for sets_file, lines in sets.items():
256                         file_name = os.path.join(set_config_dir, sets_file)
257                         f = open(file_name, "w")
258                         for line in lines:
259                                 f.write("%s\n" % line)
260                         f.close()
261
262         def _create_world(self, world):
263                 #Create /var/lib/portage/world
264                 var_lib_portage = os.path.join(self.eroot, "var", "lib", "portage")
265                 os.makedirs(var_lib_portage)
266
267                 world_file = os.path.join(var_lib_portage, "world")
268
269                 f = open(world_file, "w")
270                 for atom in world:
271                         f.write("%s\n" % atom)
272                 f.close()
273
274         def _load_config(self):
275                 env = {
276                         "ACCEPT_KEYWORDS": "x86",
277                         "PORTDIR": self.portdir,
278                         'PORTAGE_TMPDIR'       : os.path.join(self.eroot, 'var/tmp'),
279                 }
280
281                 # Pass along PORTAGE_USERNAME and PORTAGE_GRPNAME since they
282                 # need to be inherited by ebuild subprocesses.
283                 if 'PORTAGE_USERNAME' in os.environ:
284                         env['PORTAGE_USERNAME'] = os.environ['PORTAGE_USERNAME']
285                 if 'PORTAGE_GRPNAME' in os.environ:
286                         env['PORTAGE_GRPNAME'] = os.environ['PORTAGE_GRPNAME']
287
288                 settings = config(_eprefix=self.eprefix, env=env)
289                 settings.lock()
290
291                 trees = {
292                         self.root: {
293                                         "vartree": vartree(settings=settings),
294                                         "porttree": portagetree(self.root, settings=settings),
295                                         "bintree": binarytree(self.root,
296                                                 os.path.join(self.eroot, "usr/portage/packages"),
297                                                 settings=settings)
298                                 }
299                         }
300
301                 for root, root_trees in trees.items():
302                         settings = root_trees["vartree"].settings
303                         settings._init_dirs()
304                         setconfig = load_default_config(settings, root_trees)
305                         root_trees["root_config"] = RootConfig(settings, root_trees, setconfig)
306                 
307                 return settings, trees
308
309         def run(self, atoms, options={}, action=None):
310                 options = options.copy()
311                 options["--pretend"] = True
312                 options["--quiet"] = True
313                 if self.debug:
314                         options["--debug"] = True
315
316                 if not self.debug:
317                         portage.util.noiselimit = -2
318                 params = create_depgraph_params(options, action)
319                 success, depgraph, favorites = backtrack_depgraph(
320                         self.settings, self.trees, options, params, action, atoms, None)
321                 depgraph.display_problems()
322                 result = ResolverPlaygroundResult(atoms, success, depgraph, favorites)
323                 portage.util.noiselimit = 0
324
325                 return result
326
327         def run_TestCase(self, test_case):
328                 if not isinstance(test_case, ResolverPlaygroundTestCase):
329                         raise TypeError("ResolverPlayground needs a ResolverPlaygroundTestCase")
330                 for atoms in test_case.requests:
331                         result = self.run(atoms, test_case.options, test_case.action)
332                         if not test_case.compare_with_result(result):
333                                 return
334
335         def cleanup(self):
336                 portdb = self.trees[self.root]["porttree"].dbapi
337                 portdb.close_caches()
338                 portage.dbapi.porttree.portdbapi.portdbapi_instances.remove(portdb)
339                 if self.debug:
340                         print("\nEROOT=%s" % self.eroot)
341                 else:
342                         shutil.rmtree(self.eroot)
343
344 class ResolverPlaygroundTestCase(object):
345
346         def __init__(self, request, **kwargs):
347                 self.checks = {
348                         "success": None,
349                         "mergelist": None,
350                         "use_changes": None,
351                         "unstable_keywords": None,
352                         "slot_collision_solutions": None,
353                         "circular_dependency_solutions": None,
354                         }
355                 
356                 self.all_permutations = kwargs.pop("all_permutations", False)
357                 self.ignore_mergelist_order = kwargs.pop("ignore_mergelist_order", False)
358
359                 if self.all_permutations:
360                         self.requests = list(permutations(request))
361                 else:
362                         self.requests = [request]
363
364                 self.options = kwargs.pop("options", {})
365                 self.action = kwargs.pop("action", None)
366                 self.test_success = True
367                 self.fail_msg = None
368                 
369                 for key, value in kwargs.items():
370                         if not key in self.checks:
371                                 raise KeyError("Not an avaiable check: '%s'" % key)
372                         self.checks[key] = value
373         
374         def compare_with_result(self, result):
375                 fail_msgs = []
376                 for key, value in self.checks.items():
377                         got = getattr(result, key)
378                         expected = value
379                         if key == "mergelist" and self.ignore_mergelist_order and got is not None :
380                                 got = set(got)
381                                 expected = set(expected)
382                         elif key == "unstable_keywords" and expected is not None:
383                                 expected = set(expected)
384
385                         if got != expected:
386                                 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
387                                         key + ", expected: " + str(expected) + ", got: " + str(got))
388                 if fail_msgs:
389                         self.test_success = False
390                         self.fail_msg = "\n".join(fail_msgs)
391                         return False
392                 return True
393
394 class ResolverPlaygroundResult(object):
395         def __init__(self, atoms, success, mydepgraph, favorites):
396                 self.atoms = atoms
397                 self.success = success
398                 self.depgraph = mydepgraph
399                 self.favorites = favorites
400                 self.mergelist = None
401                 self.use_changes = None
402                 self.unstable_keywords = None
403                 self.slot_collision_solutions = None
404                 self.circular_dependency_solutions = None
405
406                 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
407                         self.mergelist = []
408                         for x in self.depgraph._dynamic_config._serialized_tasks_cache:
409                                 if isinstance(x, Blocker):
410                                         self.mergelist.append(x.atom)
411                                 else:
412                                         self.mergelist.append(x.cpv)
413
414                 if self.depgraph._dynamic_config._needed_use_config_changes:
415                         self.use_changes = {}
416                         for pkg, needed_use_config_changes in \
417                                 self.depgraph._dynamic_config._needed_use_config_changes.items():
418                                 new_use, changes = needed_use_config_changes
419                                 self.use_changes[pkg.cpv] = changes
420
421                 if self.depgraph._dynamic_config._needed_unstable_keywords:
422                         self.unstable_keywords = set()
423                         for pkg in self.depgraph._dynamic_config._needed_unstable_keywords:
424                                 self.unstable_keywords.add(pkg.cpv)
425
426                 if self.depgraph._dynamic_config._slot_conflict_handler is not None:
427                         self.slot_collision_solutions  = []
428                         handler = self.depgraph._dynamic_config._slot_conflict_handler
429
430                         for solution in handler.solutions:
431                                 s = {}
432                                 for pkg in solution:
433                                         changes = {}
434                                         for flag, state in solution[pkg].items():
435                                                 if state == "enabled":
436                                                         changes[flag] = True
437                                                 else:
438                                                         changes[flag] = False
439                                         s[pkg.cpv] = changes
440                                 self.slot_collision_solutions.append(s)
441
442                 if self.depgraph._dynamic_config._circular_dependency_handler is not None:
443                         handler = self.depgraph._dynamic_config._circular_dependency_handler
444                         sol = handler.solutions
445                         self.circular_dependency_solutions = dict( zip([x.cpv for x in sol.keys()], sol.values()) )
446