Pass along PORTAGE_USERNAME and PORTAGE_GRPNAME inside _load_config()
[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                 # Pass along PORTAGE_USERNAME and PORTAGE_GRPNAME since they
202                 # need to be inherited by ebuild subprocesses.
203                 env = {
204                         "ACCEPT_KEYWORDS": "x86",
205                         "PORTDIR": self.portdir,
206                         "ROOT": self.root,
207                         'PORTAGE_TMPDIR'       : os.path.join(self.root, 'var/tmp'),
208                         'PORTAGE_USERNAME'     : os.environ["PORTAGE_USERNAME"],
209                         'PORTAGE_GRPNAME'      : os.environ["PORTAGE_GRPNAME"],
210                 }
211
212                 settings = config(config_root=self.root, target_root=self.root, env=env)
213                 settings.lock()
214
215                 trees = {
216                         self.root: {
217                                         "virtuals": settings.getvirtuals(),
218                                         "vartree": vartree(self.root, categories=settings.categories, settings=settings),
219                                         "porttree": portagetree(self.root, settings=settings),
220                                         "bintree": binarytree(self.root, os.path.join(self.root, "usr/portage/packages"), settings=settings)
221                                 }
222                         }
223
224                 for root, root_trees in trees.items():
225                         settings = root_trees["vartree"].settings
226                         settings._init_dirs()
227                         setconfig = SetConfig([], settings, root_trees)
228                         root_trees["root_config"] = RootConfig(settings, root_trees, setconfig)
229                         setconfig_fallback(root_trees["root_config"])
230                 
231                 return settings, trees
232
233         def run(self, atoms, options={}, action=None):
234                 options = options.copy()
235                 options["--pretend"] = True
236                 options["--quiet"] = True
237                 options["--root"] = self.root
238                 options["--config-root"] = self.root
239                 options["--root-deps"] = "rdeps"
240                 if self.debug:
241                         options["--debug"] = True
242                 # Add a fake _test_ option that can be used for
243                 # conditional test code.
244                 options["_test_"] = True
245
246                 if not self.debug:
247                         portage.util.noiselimit = -2
248                 params = create_depgraph_params(options, action)
249                 success, depgraph, favorites = backtrack_depgraph(
250                         self.settings, self.trees, options, params, action, atoms, None)
251                 depgraph.display_problems()
252                 result = ResolverPlaygroundResult(atoms, success, depgraph, favorites)
253                 portage.util.noiselimit = 0
254
255                 return result
256
257         def run_TestCase(self, test_case):
258                 if not isinstance(test_case, ResolverPlaygroundTestCase):
259                         raise TypeError("ResolverPlayground needs a ResolverPlaygroundTestCase")
260                 for atoms in test_case.requests:
261                         result = self.run(atoms, test_case.options, test_case.action)
262                         if not test_case.compare_with_result(result):
263                                 return
264
265         def cleanup(self):
266                 if self.debug:
267                         print("\nROOT=%s" % self.root)
268                 else:
269                         shutil.rmtree(self.root)
270
271 class ResolverPlaygroundTestCase(object):
272
273         def __init__(self, request, **kwargs):
274                 self.checks = {
275                         "success": None,
276                         "mergelist": None,
277                         "use_changes": None,
278                         "unstable_keywords": None,
279                         "slot_collision_solutions": None,
280                         }
281                 
282                 self.all_permutations = kwargs.pop("all_permutations", False)
283                 self.ignore_mergelist_order = kwargs.pop("ignore_mergelist_order", False)
284
285                 if self.all_permutations:
286                         self.requests = list(permutations(request))
287                 else:
288                         self.requests = [request]
289
290                 self.options = kwargs.pop("options", {})
291                 self.action = kwargs.pop("action", None)
292                 self.test_success = True
293                 self.fail_msg = None
294                 
295                 for key, value in kwargs.items():
296                         if not key in self.checks:
297                                 raise KeyError("Not an avaiable check: '%s'" % key)
298                         self.checks[key] = value
299         
300         def compare_with_result(self, result):
301                 fail_msgs = []
302                 for key, value in self.checks.items():
303                         got = getattr(result, key)
304                         expected = value
305                         if key == "mergelist" and self.ignore_mergelist_order and got is not None :
306                                 got = set(got)
307                                 expected = set(expected)
308                         elif key == "unstable_keywords" and expected is not None:
309                                 expected = set(expected)
310
311                         if got != expected:
312                                 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
313                                         key + ", expected: " + str(expected) + ", got: " + str(got))
314                 if fail_msgs:
315                         self.test_success = False
316                         self.fail_msg = "\n".join(fail_msgs)
317                         return False
318                 return True
319
320 class ResolverPlaygroundResult(object):
321         def __init__(self, atoms, success, mydepgraph, favorites):
322                 self.atoms = atoms
323                 self.success = success
324                 self.depgraph = mydepgraph
325                 self.favorites = favorites
326                 self.mergelist = None
327                 self.use_changes = None
328                 self.unstable_keywords = None
329                 self.slot_collision_solutions = None
330
331                 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
332                         self.mergelist = []
333                         for x in self.depgraph._dynamic_config._serialized_tasks_cache:
334                                 if isinstance(x, Blocker):
335                                         self.mergelist.append(x.atom)
336                                 else:
337                                         self.mergelist.append(x.cpv)
338
339                 if self.depgraph._dynamic_config._needed_use_config_changes:
340                         self.use_changes = {}
341                         for pkg, needed_use_config_changes in \
342                                 self.depgraph._dynamic_config._needed_use_config_changes.items():
343                                 new_use, changes = needed_use_config_changes
344                                 self.use_changes[pkg.cpv] = changes
345
346                 if self.depgraph._dynamic_config._needed_unstable_keywords:
347                         self.unstable_keywords = set()
348                         for pkg in self.depgraph._dynamic_config._needed_unstable_keywords:
349                                 self.unstable_keywords.add(pkg.cpv)
350
351                 if self.depgraph._dynamic_config._slot_conflict_handler is not None:
352                         self.slot_collision_solutions  = []
353                         handler = self.depgraph._dynamic_config._slot_conflict_handler
354
355                         for solution in handler.solutions:
356                                 s = {}
357                                 for pkg in solution:
358                                         changes = {}
359                                         for flag, state in solution[pkg].items():
360                                                 if state == "enabled":
361                                                         changes[flag] = True
362                                                 else:
363                                                         changes[flag] = False
364                                         s[pkg.cpv] = changes
365                                 self.slot_collision_solutions.append(s)