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