Avoid KeyError if PORTAGE_USERNAME or PORTAGE_GRPNAME are unset.
[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={}, world=[], debug=False):
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.debug = debug
38                 self.root = tempfile.mkdtemp() + os.path.sep
39                 self.portdir = os.path.join(self.root, "usr/portage")
40                 self.vdbdir = os.path.join(self.root, "var/db/pkg")
41                 os.makedirs(self.portdir)
42                 os.makedirs(self.vdbdir)
43                 
44                 self._create_ebuilds(ebuilds)
45                 self._create_installed(installed)
46                 self._create_profile(ebuilds, installed, profile)
47                 self._create_world(world)
48                 
49                 self.settings, self.trees = self._load_config()
50                 
51                 self._create_ebuild_manifests(ebuilds)
52
53         def _create_ebuilds(self, ebuilds):
54                 for cpv in ebuilds:
55                         a = Atom("=" + cpv)
56                         ebuild_dir = os.path.join(self.portdir, a.cp)
57                         ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
58                         try:
59                                 os.makedirs(ebuild_dir)
60                         except os.error:
61                                 pass
62                         
63                         metadata = ebuilds[cpv]
64                         eapi = metadata.get("EAPI", 0)
65                         slot = metadata.get("SLOT", 0)
66                         keywords = metadata.get("KEYWORDS", "x86")
67                         iuse = metadata.get("IUSE", "")
68                         depend = metadata.get("DEPEND", "")
69                         rdepend = metadata.get("RDEPEND", None)
70                         pdepend = metadata.get("PDEPEND", None)
71                         required_use = metadata.get("REQUIRED_USE", None)
72
73                         f = open(ebuild_path, "w")
74                         f.write('EAPI="' + str(eapi) + '"\n')
75                         f.write('SLOT="' + str(slot) + '"\n')
76                         f.write('KEYWORDS="' + str(keywords) + '"\n')
77                         f.write('IUSE="' + str(iuse) + '"\n')
78                         f.write('DEPEND="' + str(depend) + '"\n')
79                         if rdepend is not None:
80                                 f.write('RDEPEND="' + str(rdepend) + '"\n')
81                         if pdepend is not None:
82                                 f.write('PDEPEND="' + str(pdepend) + '"\n')
83                         if required_use is not None:
84                                 f.write('REQUIRED_USE="' + str(required_use) + '"\n')
85                         f.close()
86
87         def _create_ebuild_manifests(self, ebuilds):
88                 for cpv in ebuilds:
89                         a = Atom("=" + cpv)
90                         ebuild_dir = os.path.join(self.portdir, a.cp)
91                         ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
92                         
93                         portage.util.noiselimit = -1
94                         tmpsettings = config(clone=self.settings)
95                         portdb = self.trees[self.root]["porttree"].dbapi
96                         portage.doebuild(ebuild_path, "digest", self.root, tmpsettings,
97                                 tree="porttree", mydbapi=portdb)
98                         portage.util.noiselimit = 0
99                 
100         def _create_installed(self, installed):
101                 for cpv in installed:
102                         a = Atom("=" + cpv)
103                         vdb_pkg_dir = os.path.join(self.vdbdir, a.cpv)
104                         try:
105                                 os.makedirs(vdb_pkg_dir)
106                         except os.error:
107                                 pass
108
109                         metadata = installed[cpv]
110                         eapi = metadata.get("EAPI", 0)
111                         slot = metadata.get("SLOT", 0)
112                         keywords = metadata.get("KEYWORDS", "~x86")
113                         iuse = metadata.get("IUSE", "")
114                         use = metadata.get("USE", "")
115                         depend = metadata.get("DEPEND", "")
116                         rdepend = metadata.get("RDEPEND", None)
117                         pdepend = metadata.get("PDEPEND", None)
118                         required_use = metadata.get("REQUIRED_USE", None)
119                         
120                         def write_key(key, value):
121                                 f = open(os.path.join(vdb_pkg_dir, key), "w")
122                                 f.write(str(value) + "\n")
123                                 f.close()
124                         
125                         write_key("EAPI", eapi)
126                         write_key("SLOT", slot)
127                         write_key("KEYWORDS", keywords)
128                         write_key("IUSE", iuse)
129                         write_key("USE", use)
130                         write_key("DEPEND", depend)
131                         if rdepend is not None:
132                                 write_key("RDEPEND", rdepend)
133                         if pdepend is not None:
134                                 write_key("PDEPEND", pdepend)
135                         if required_use is not None:
136                                 write_key("REQUIRED_USE", required_use)
137
138         def _create_profile(self, ebuilds, installed, profile):
139                 #Create $PORTDIR/profiles/categories
140                 categories = set()
141                 for cpv in chain(ebuilds.keys(), installed.keys()):
142                         categories.add(catsplit(cpv)[0])
143                 
144                 profile_dir = os.path.join(self.portdir, "profiles")
145                 try:
146                         os.makedirs(profile_dir)
147                 except os.error:
148                         pass
149                 
150                 categories_file = os.path.join(profile_dir, "categories")
151                 
152                 f = open(categories_file, "w")
153                 for cat in categories:
154                         f.write(cat + "\n")
155                 f.close()
156                 
157                 #Create $profile_dir/eclass (we fail to digest the ebuilds if it's not there)
158                 os.makedirs(os.path.join(self.portdir, "eclass"))
159
160                 sub_profile_dir = os.path.join(profile_dir, "default", "linux", "x86", "test_profile")
161                 os.makedirs(sub_profile_dir)
162                 
163                 eapi_file = os.path.join(sub_profile_dir, "eapi")
164                 f = open(eapi_file, "w")
165                 f.write("0\n")
166                 f.close()
167                 
168                 make_defaults_file = os.path.join(sub_profile_dir, "make.defaults")
169                 f = open(make_defaults_file, "w")
170                 f.write("ARCH=\"x86\"\n")
171                 f.write("ACCEPT_KEYWORDS=\"x86\"\n")
172                 f.close()
173                 
174                 use_force_file = os.path.join(sub_profile_dir, "use.force")
175                 f = open(use_force_file, "w")
176                 f.write("x86\n")
177                 f.close()
178
179                 if profile:
180                         #This is meant to allow the consumer to set up his own profile,
181                         #with package.mask and what not.
182                         raise NotImplentedError()
183                 
184                 #Create profile symlink
185                 os.makedirs(os.path.join(self.root, "etc"))
186                 os.symlink(sub_profile_dir, os.path.join(self.root, "etc", "make.profile"))
187
188         def _create_world(self, world):
189                 #Create /var/lib/portage/world
190                 var_lib_portage = os.path.join(self.root, "var", "lib", "portage")
191                 os.makedirs(var_lib_portage)
192
193                 world_file = os.path.join(var_lib_portage, "world")
194
195                 f = open(world_file, "w")
196                 for atom in world:
197                         f.write("%s\n" % atom)
198                 f.close()
199
200         def _load_config(self):
201                 env = {
202                         "ACCEPT_KEYWORDS": "x86",
203                         "PORTDIR": self.portdir,
204                         "ROOT": self.root,
205                         'PORTAGE_TMPDIR'       : os.path.join(self.root, 'var/tmp'),
206                 }
207
208                 # Pass along PORTAGE_USERNAME and PORTAGE_GRPNAME since they
209                 # need to be inherited by ebuild subprocesses.
210                 if 'PORTAGE_USERNAME' in os.environ:
211                         env['PORTAGE_USERNAME'] = os.environ['PORTAGE_USERNAME']
212                 if 'PORTAGE_GRPNAME' in os.environ:
213                         env['PORTAGE_GRPNAME'] = os.environ['PORTAGE_GRPNAME']
214
215                 settings = config(config_root=self.root, target_root=self.root, env=env)
216                 settings.lock()
217
218                 trees = {
219                         self.root: {
220                                         "virtuals": settings.getvirtuals(),
221                                         "vartree": vartree(self.root, categories=settings.categories, settings=settings),
222                                         "porttree": portagetree(self.root, settings=settings),
223                                         "bintree": binarytree(self.root, os.path.join(self.root, "usr/portage/packages"), settings=settings)
224                                 }
225                         }
226
227                 for root, root_trees in trees.items():
228                         settings = root_trees["vartree"].settings
229                         settings._init_dirs()
230                         setconfig = SetConfig([], settings, root_trees)
231                         root_trees["root_config"] = RootConfig(settings, root_trees, setconfig)
232                         setconfig_fallback(root_trees["root_config"])
233                 
234                 return settings, trees
235
236         def run(self, atoms, options={}, action=None):
237                 options = options.copy()
238                 options["--pretend"] = True
239                 options["--quiet"] = True
240                 options["--root"] = self.root
241                 options["--config-root"] = self.root
242                 options["--root-deps"] = "rdeps"
243                 if self.debug:
244                         options["--debug"] = True
245                 # Add a fake _test_ option that can be used for
246                 # conditional test code.
247                 options["_test_"] = True
248
249                 if not self.debug:
250                         portage.util.noiselimit = -2
251                 params = create_depgraph_params(options, action)
252                 success, depgraph, favorites = backtrack_depgraph(
253                         self.settings, self.trees, options, params, action, atoms, None)
254                 depgraph.display_problems()
255                 result = ResolverPlaygroundResult(atoms, success, depgraph, favorites)
256                 portage.util.noiselimit = 0
257
258                 return result
259
260         def run_TestCase(self, test_case):
261                 if not isinstance(test_case, ResolverPlaygroundTestCase):
262                         raise TypeError("ResolverPlayground needs a ResolverPlaygroundTestCase")
263                 for atoms in test_case.requests:
264                         result = self.run(atoms, test_case.options, test_case.action)
265                         if not test_case.compare_with_result(result):
266                                 return
267
268         def cleanup(self):
269                 if self.debug:
270                         print("\nROOT=%s" % self.root)
271                 else:
272                         shutil.rmtree(self.root)
273
274 class ResolverPlaygroundTestCase(object):
275
276         def __init__(self, request, **kwargs):
277                 self.checks = {
278                         "success": None,
279                         "mergelist": None,
280                         "use_changes": None,
281                         "unstable_keywords": None,
282                         "slot_collision_solutions": None,
283                         }
284                 
285                 self.all_permutations = kwargs.pop("all_permutations", False)
286                 self.ignore_mergelist_order = kwargs.pop("ignore_mergelist_order", False)
287
288                 if self.all_permutations:
289                         self.requests = list(permutations(request))
290                 else:
291                         self.requests = [request]
292
293                 self.options = kwargs.pop("options", {})
294                 self.action = kwargs.pop("action", None)
295                 self.test_success = True
296                 self.fail_msg = None
297                 
298                 for key, value in kwargs.items():
299                         if not key in self.checks:
300                                 raise KeyError("Not an avaiable check: '%s'" % key)
301                         self.checks[key] = value
302         
303         def compare_with_result(self, result):
304                 fail_msgs = []
305                 for key, value in self.checks.items():
306                         got = getattr(result, key)
307                         expected = value
308                         if key == "mergelist" and self.ignore_mergelist_order and got is not None :
309                                 got = set(got)
310                                 expected = set(expected)
311                         elif key == "unstable_keywords" and expected is not None:
312                                 expected = set(expected)
313
314                         if got != expected:
315                                 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
316                                         key + ", expected: " + str(expected) + ", got: " + str(got))
317                 if fail_msgs:
318                         self.test_success = False
319                         self.fail_msg = "\n".join(fail_msgs)
320                         return False
321                 return True
322
323 class ResolverPlaygroundResult(object):
324         def __init__(self, atoms, success, mydepgraph, favorites):
325                 self.atoms = atoms
326                 self.success = success
327                 self.depgraph = mydepgraph
328                 self.favorites = favorites
329                 self.mergelist = None
330                 self.use_changes = None
331                 self.unstable_keywords = None
332                 self.slot_collision_solutions = None
333
334                 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
335                         self.mergelist = []
336                         for x in self.depgraph._dynamic_config._serialized_tasks_cache:
337                                 if isinstance(x, Blocker):
338                                         self.mergelist.append(x.atom)
339                                 else:
340                                         self.mergelist.append(x.cpv)
341
342                 if self.depgraph._dynamic_config._needed_use_config_changes:
343                         self.use_changes = {}
344                         for pkg, needed_use_config_changes in \
345                                 self.depgraph._dynamic_config._needed_use_config_changes.items():
346                                 new_use, changes = needed_use_config_changes
347                                 self.use_changes[pkg.cpv] = changes
348
349                 if self.depgraph._dynamic_config._needed_unstable_keywords:
350                         self.unstable_keywords = set()
351                         for pkg in self.depgraph._dynamic_config._needed_unstable_keywords:
352                                 self.unstable_keywords.add(pkg.cpv)
353
354                 if self.depgraph._dynamic_config._slot_conflict_handler is not None:
355                         self.slot_collision_solutions  = []
356                         handler = self.depgraph._dynamic_config._slot_conflict_handler
357
358                         for solution in handler.solutions:
359                                 s = {}
360                                 for pkg in solution:
361                                         changes = {}
362                                         for flag, state in solution[pkg].items():
363                                                 if state == "enabled":
364                                                         changes[flag] = True
365                                                 else:
366                                                         changes[flag] = False
367                                         s[pkg.cpv] = changes
368                                 self.slot_collision_solutions.append(s)