324ef9dd782c33a91b4f570688a025f275d3683c
[portage.git] / pym / portage / tests / resolver / ResolverPlayground.py
1 # Copyright 2010-2012 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 from itertools import permutations
5 import sys
6 import tempfile
7 import portage
8 from portage import os
9 from portage import shutil
10 from portage.const import (GLOBAL_CONFIG_PATH, PORTAGE_BASE_PATH,
11         USER_CONFIG_PATH)
12 from portage.dep import Atom, _repo_separator
13 from portage.package.ebuild.config import config
14 from portage.package.ebuild.digestgen import digestgen
15 from portage._sets import load_default_config
16 from portage._sets.base import InternalPackageSet
17 from portage.util import ensure_dirs, normalize_path
18 from portage.versions import catsplit
19
20 import _emerge
21 from _emerge.actions import calc_depclean
22 from _emerge.Blocker import Blocker
23 from _emerge.create_depgraph_params import create_depgraph_params
24 from _emerge.depgraph import backtrack_depgraph
25 from _emerge.RootConfig import RootConfig
26
27 if sys.hexversion >= 0x3000000:
28         basestring = str
29
30 class ResolverPlayground(object):
31         """
32         This class helps to create the necessary files on disk and
33         the needed settings instances, etc. for the resolver to do
34         its work.
35         """
36
37         config_files = frozenset(("eapi", "package.accept_keywords", "package.use",
38                 "package.use.stable.mask", "package.mask", "package.keywords",
39                 "package.unmask", "package.properties", "package.license", "use.mask", "use.force",
40                 "layout.conf",))
41
42         metadata_xml_template = """<?xml version="1.0" encoding="UTF-8"?>
43 <!DOCTYPE pkgmetadata SYSTEM "http://www.gentoo.org/dtd/metadata.dtd">
44 <pkgmetadata>
45 <herd>%(herd)s</herd>
46 <maintainer>
47 <email>maintainer-needed@gentoo.org</email>
48 <description>Description of the maintainership</description>
49 </maintainer>
50 <longdescription>Long description of the package</longdescription>
51 <use>
52 %(flags)s
53 </use>
54 </pkgmetadata>
55 """
56
57         def __init__(self, ebuilds={}, binpkgs={}, installed={}, profile={}, repo_configs={}, \
58                 user_config={}, sets={}, world=[], world_sets=[], distfiles={}, debug=False):
59                 """
60                 ebuilds: cpv -> metadata mapping simulating available ebuilds. 
61                 installed: cpv -> metadata mapping simulating installed packages.
62                         If a metadata key is missing, it gets a default value.
63                 profile: settings defined by the profile.
64                 """
65                 self.debug = debug
66                 self.eprefix = normalize_path(tempfile.mkdtemp())
67                 self.eroot = self.eprefix + os.sep
68                 self.distdir = os.path.join(self.eroot, "var", "portage", "distfiles")
69                 self.pkgdir = os.path.join(self.eprefix, "pkgdir")
70                 self.portdir = os.path.join(self.eroot, "usr/portage")
71                 self.vdbdir = os.path.join(self.eroot, "var/db/pkg")
72                 os.makedirs(self.portdir)
73                 os.makedirs(self.vdbdir)
74
75                 if not debug:
76                         portage.util.noiselimit = -2
77
78                 self.repo_dirs = {}
79                 #Make sure the main repo is always created
80                 self._get_repo_dir("test_repo")
81
82                 self._create_distfiles(distfiles)
83                 self._create_ebuilds(ebuilds)
84                 self._create_binpkgs(binpkgs)
85                 self._create_installed(installed)
86                 self._create_profile(ebuilds, installed, profile, repo_configs, user_config, sets)
87                 self._create_world(world, world_sets)
88
89                 self.settings, self.trees = self._load_config()
90
91                 self._create_ebuild_manifests(ebuilds)
92                 
93                 portage.util.noiselimit = 0
94
95         def _get_repo_dir(self, repo):
96                 """
97                 Create the repo directory if needed.
98                 """
99                 if repo not in self.repo_dirs:
100                         if repo == "test_repo":
101                                 repo_path = self.portdir
102                         else:
103                                 repo_path = os.path.join(self.eroot, "usr", "local", repo)
104
105                         self.repo_dirs[repo] = repo_path
106                         profile_path = os.path.join(repo_path, "profiles")
107
108                         try:
109                                 os.makedirs(profile_path)
110                         except os.error:
111                                 pass
112
113                         repo_name_file = os.path.join(profile_path, "repo_name")
114                         f = open(repo_name_file, "w")
115                         f.write("%s\n" % repo)
116                         f.close()
117
118                 return self.repo_dirs[repo]
119
120         def _create_distfiles(self, distfiles):
121                 os.makedirs(self.distdir)
122                 for k, v in distfiles.items():
123                         with open(os.path.join(self.distdir, k), 'wb') as f:
124                                 f.write(v)
125
126         def _create_ebuilds(self, ebuilds):
127                 for cpv in ebuilds:
128                         a = Atom("=" + cpv, allow_repo=True)
129                         repo = a.repo
130                         if repo is None:
131                                 repo = "test_repo"
132
133                         metadata = ebuilds[cpv].copy()
134                         copyright_header = metadata.pop("COPYRIGHT_HEADER", None)
135                         desc = metadata.pop("DESCRIPTION", None)
136                         eapi = metadata.pop("EAPI", 0)
137                         lic = metadata.pop("LICENSE", "")
138                         properties = metadata.pop("PROPERTIES", "")
139                         slot = metadata.pop("SLOT", 0)
140                         keywords = metadata.pop("KEYWORDS", "x86")
141                         homepage = metadata.pop("HOMEPAGE", None)
142                         src_uri = metadata.pop("SRC_URI", None)
143                         iuse = metadata.pop("IUSE", "")
144                         provide = metadata.pop("PROVIDE", None)
145                         depend = metadata.pop("DEPEND", "")
146                         rdepend = metadata.pop("RDEPEND", None)
147                         pdepend = metadata.pop("PDEPEND", None)
148                         required_use = metadata.pop("REQUIRED_USE", None)
149                         misc_content = metadata.pop("MISC_CONTENT", None)
150
151                         if metadata:
152                                 raise ValueError("metadata of ebuild '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
153
154                         repo_dir = self._get_repo_dir(repo)
155                         ebuild_dir = os.path.join(repo_dir, a.cp)
156                         ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
157                         try:
158                                 os.makedirs(ebuild_dir)
159                         except os.error:
160                                 pass
161
162                         f = open(ebuild_path, "w")
163                         if copyright_header is not None:
164                                 f.write(copyright_header)
165                         f.write('EAPI="' + str(eapi) + '"\n')
166                         if desc is not None:
167                                 f.write('DESCRIPTION="%s"\n' % desc)
168                         if homepage is not None:
169                                 f.write('HOMEPAGE="%s"\n' % homepage)
170                         if src_uri is not None:
171                                 f.write('SRC_URI="%s"\n' % src_uri)
172                         f.write('LICENSE="' + str(lic) + '"\n')
173                         f.write('PROPERTIES="' + str(properties) + '"\n')
174                         f.write('SLOT="' + str(slot) + '"\n')
175                         f.write('KEYWORDS="' + str(keywords) + '"\n')
176                         f.write('IUSE="' + str(iuse) + '"\n')
177                         if provide is not None:
178                                 f.write('PROVIDE="%s"\n' % provide)
179                         f.write('DEPEND="' + str(depend) + '"\n')
180                         if rdepend is not None:
181                                 f.write('RDEPEND="' + str(rdepend) + '"\n')
182                         if pdepend is not None:
183                                 f.write('PDEPEND="' + str(pdepend) + '"\n')
184                         if required_use is not None:
185                                 f.write('REQUIRED_USE="' + str(required_use) + '"\n')
186                         if misc_content is not None:
187                                 f.write(misc_content)
188                         f.close()
189
190         def _create_ebuild_manifests(self, ebuilds):
191                 tmpsettings = config(clone=self.settings)
192                 tmpsettings['PORTAGE_QUIET'] = '1'
193                 for cpv in ebuilds:
194                         a = Atom("=" + cpv, allow_repo=True)
195                         repo = a.repo
196                         if repo is None:
197                                 repo = "test_repo"
198
199                         repo_dir = self._get_repo_dir(repo)
200                         ebuild_dir = os.path.join(repo_dir, a.cp)
201                         ebuild_path = os.path.join(ebuild_dir, a.cpv.split("/")[1] + ".ebuild")
202
203                         portdb = self.trees[self.eroot]["porttree"].dbapi
204                         tmpsettings['O'] = ebuild_dir
205                         if not digestgen(mysettings=tmpsettings, myportdb=portdb):
206                                 raise AssertionError('digest creation failed for %s' % ebuild_path)
207
208         def _create_binpkgs(self, binpkgs):
209                 for cpv, metadata in binpkgs.items():
210                         a = Atom("=" + cpv, allow_repo=True)
211                         repo = a.repo
212                         if repo is None:
213                                 repo = "test_repo"
214
215                         cat, pf = catsplit(a.cpv)
216                         metadata = metadata.copy()
217                         metadata.setdefault("SLOT", "0")
218                         metadata.setdefault("KEYWORDS", "x86")
219                         metadata.setdefault("BUILD_TIME", "0")
220                         metadata["repository"] = repo
221                         metadata["CATEGORY"] = cat
222                         metadata["PF"] = pf
223
224                         repo_dir = self.pkgdir
225                         category_dir = os.path.join(repo_dir, cat)
226                         binpkg_path = os.path.join(category_dir, pf + ".tbz2")
227                         ensure_dirs(category_dir)
228                         t = portage.xpak.tbz2(binpkg_path)
229                         t.recompose_mem(portage.xpak.xpak_mem(metadata))
230
231         def _create_installed(self, installed):
232                 for cpv in installed:
233                         a = Atom("=" + cpv, allow_repo=True)
234                         repo = a.repo
235                         if repo is None:
236                                 repo = "test_repo"
237
238                         vdb_pkg_dir = os.path.join(self.vdbdir, a.cpv)
239                         try:
240                                 os.makedirs(vdb_pkg_dir)
241                         except os.error:
242                                 pass
243
244                         metadata = installed[cpv].copy()
245                         eapi = metadata.pop("EAPI", 0)
246                         lic = metadata.pop("LICENSE", "")
247                         properties = metadata.pop("PROPERTIES", "")
248                         slot = metadata.pop("SLOT", 0)
249                         build_time = metadata.pop("BUILD_TIME", "0")
250                         keywords = metadata.pop("KEYWORDS", "~x86")
251                         iuse = metadata.pop("IUSE", "")
252                         use = metadata.pop("USE", "")
253                         provide = metadata.pop("PROVIDE", None)
254                         depend = metadata.pop("DEPEND", "")
255                         rdepend = metadata.pop("RDEPEND", None)
256                         pdepend = metadata.pop("PDEPEND", None)
257                         required_use = metadata.pop("REQUIRED_USE", None)
258
259                         if metadata:
260                                 raise ValueError("metadata of installed '%s' contains unknown keys: %s" % (cpv, metadata.keys()))
261
262                         def write_key(key, value):
263                                 f = open(os.path.join(vdb_pkg_dir, key), "w")
264                                 f.write(str(value) + "\n")
265                                 f.close()
266                         
267                         write_key("EAPI", eapi)
268                         write_key("BUILD_TIME", build_time)
269                         write_key("COUNTER", "0")
270                         write_key("LICENSE", lic)
271                         write_key("PROPERTIES", properties)
272                         write_key("SLOT", slot)
273                         write_key("LICENSE", lic)
274                         write_key("PROPERTIES", properties)
275                         write_key("repository", repo)
276                         write_key("KEYWORDS", keywords)
277                         write_key("IUSE", iuse)
278                         write_key("USE", use)
279                         if provide is not None:
280                                 write_key("PROVIDE", provide)
281                         write_key("DEPEND", depend)
282                         if rdepend is not None:
283                                 write_key("RDEPEND", rdepend)
284                         if pdepend is not None:
285                                 write_key("PDEPEND", pdepend)
286                         if required_use is not None:
287                                 write_key("REQUIRED_USE", required_use)
288
289         def _create_profile(self, ebuilds, installed, profile, repo_configs, user_config, sets):
290
291                 user_config_dir = os.path.join(self.eroot, USER_CONFIG_PATH)
292
293                 try:
294                         os.makedirs(user_config_dir)
295                 except os.error:
296                         pass
297
298                 for repo in self.repo_dirs:
299                         repo_dir = self._get_repo_dir(repo)
300                         profile_dir = os.path.join(self._get_repo_dir(repo), "profiles")
301                         metadata_dir = os.path.join(repo_dir, "metadata")
302                         os.makedirs(metadata_dir)
303
304                         #Create $REPO/profiles/categories
305                         categories = set()
306                         for cpv in ebuilds:
307                                 ebuilds_repo = Atom("="+cpv, allow_repo=True).repo
308                                 if ebuilds_repo is None:
309                                         ebuilds_repo = "test_repo"
310                                 if ebuilds_repo == repo:
311                                         categories.add(catsplit(cpv)[0])
312
313                         categories_file = os.path.join(profile_dir, "categories")
314                         f = open(categories_file, "w")
315                         for cat in categories:
316                                 f.write(cat + "\n")
317                         f.close()
318                         
319                         #Create $REPO/profiles/license_groups
320                         license_file = os.path.join(profile_dir, "license_groups")
321                         f = open(license_file, "w")
322                         f.write("EULA TEST\n")
323                         f.close()
324
325                         repo_config = repo_configs.get(repo) 
326                         if repo_config:
327                                 for config_file, lines in repo_config.items():
328                                         if config_file not in self.config_files:
329                                                 raise ValueError("Unknown config file: '%s'" % config_file)
330
331                                         if config_file in ("layout.conf",):
332                                                 file_name = os.path.join(repo_dir, "metadata", config_file)
333                                         else:
334                                                 file_name = os.path.join(profile_dir, config_file)
335                                         f = open(file_name, "w")
336                                         for line in lines:
337                                                 f.write("%s\n" % line)
338                                         f.close()
339
340                         #Create $profile_dir/eclass (we fail to digest the ebuilds if it's not there)
341                         os.makedirs(os.path.join(repo_dir, "eclass"))
342
343                         if repo == "test_repo":
344                                 #Create a minimal profile in /usr/portage
345                                 sub_profile_dir = os.path.join(profile_dir, "default", "linux", "x86", "test_profile")
346                                 os.makedirs(sub_profile_dir)
347
348                                 if not (profile and "eapi" in profile):
349                                         eapi_file = os.path.join(sub_profile_dir, "eapi")
350                                         f = open(eapi_file, "w")
351                                         f.write("0\n")
352                                         f.close()
353
354                                 make_defaults_file = os.path.join(sub_profile_dir, "make.defaults")
355                                 f = open(make_defaults_file, "w")
356                                 f.write("ARCH=\"x86\"\n")
357                                 f.write("ACCEPT_KEYWORDS=\"x86\"\n")
358                                 f.close()
359
360                                 use_force_file = os.path.join(sub_profile_dir, "use.force")
361                                 f = open(use_force_file, "w")
362                                 f.write("x86\n")
363                                 f.close()
364
365                                 parent_file = os.path.join(sub_profile_dir, "parent")
366                                 f = open(parent_file, "w")
367                                 f.write("..\n")
368                                 f.close()
369
370                                 if profile:
371                                         for config_file, lines in profile.items():
372                                                 if config_file not in self.config_files:
373                                                         raise ValueError("Unknown config file: '%s'" % config_file)
374
375                                                 file_name = os.path.join(sub_profile_dir, config_file)
376                                                 f = open(file_name, "w")
377                                                 for line in lines:
378                                                         f.write("%s\n" % line)
379                                                 f.close()
380
381                                 #Create profile symlink
382                                 os.symlink(sub_profile_dir, os.path.join(user_config_dir, "make.profile"))
383
384                                 #Create minimal herds.xml
385                                 herds_xml = """<?xml version="1.0" encoding="UTF-8"?>
386 <!DOCTYPE herds SYSTEM "http://www.gentoo.org/dtd/herds.dtd">
387 <?xml-stylesheet href="/xsl/herds.xsl" type="text/xsl" ?>
388 <?xml-stylesheet href="/xsl/guide.xsl" type="text/xsl" ?>
389 <herds>
390 <herd>
391   <name>base-system</name>
392   <email>base-system@gentoo.org</email>
393   <description>Core system utilities and libraries.</description>
394   <maintainer>
395     <email>base-system@gentoo.orgg</email>
396     <name>Base System</name>
397     <role>Base System Maintainer</role>
398   </maintainer>
399 </herd>
400 </herds>
401 """
402                                 with open(os.path.join(metadata_dir, "metadata.xml"), 'w') as f:
403                                         f.write(herds_xml)
404
405                 # Write empty entries for each repository, in order to exercise
406                 # RepoConfigLoader's repos.conf processing.
407                 repos_conf_file = os.path.join(user_config_dir, "repos.conf")           
408                 f = open(repos_conf_file, "w")
409                 for repo in sorted(self.repo_dirs.keys()):
410                         f.write("[%s]\n" % repo)
411                         f.write("\n")
412                 f.close()
413
414                 for config_file, lines in user_config.items():
415                         if config_file not in self.config_files:
416                                 raise ValueError("Unknown config file: '%s'" % config_file)
417
418                         file_name = os.path.join(user_config_dir, config_file)
419                         f = open(file_name, "w")
420                         for line in lines:
421                                 f.write("%s\n" % line)
422                         f.close()
423
424                 #Create /usr/share/portage/config/make.globals
425                 make_globals_path = os.path.join(self.eroot,
426                         GLOBAL_CONFIG_PATH.lstrip(os.sep), "make.globals")
427                 ensure_dirs(os.path.dirname(make_globals_path))
428                 os.symlink(os.path.join(PORTAGE_BASE_PATH, "cnf", "make.globals"),
429                         make_globals_path)
430
431                 #Create /usr/share/portage/config/sets/portage.conf
432                 default_sets_conf_dir = os.path.join(self.eroot, "usr/share/portage/config/sets")
433                 
434                 try:
435                         os.makedirs(default_sets_conf_dir)
436                 except os.error:
437                         pass
438
439                 provided_sets_portage_conf = \
440                         os.path.join(PORTAGE_BASE_PATH, "cnf/sets/portage.conf")
441                 os.symlink(provided_sets_portage_conf, os.path.join(default_sets_conf_dir, "portage.conf"))
442
443                 set_config_dir = os.path.join(user_config_dir, "sets")
444
445                 try:
446                         os.makedirs(set_config_dir)
447                 except os.error:
448                         pass
449
450                 for sets_file, lines in sets.items():
451                         file_name = os.path.join(set_config_dir, sets_file)
452                         f = open(file_name, "w")
453                         for line in lines:
454                                 f.write("%s\n" % line)
455                         f.close()
456
457                 user_config_dir = os.path.join(self.eroot, "etc", "portage")
458
459                 try:
460                         os.makedirs(user_config_dir)
461                 except os.error:
462                         pass
463
464                 for config_file, lines in user_config.items():
465                         if config_file not in self.config_files:
466                                 raise ValueError("Unknown config file: '%s'" % config_file)
467
468                         file_name = os.path.join(user_config_dir, config_file)
469                         f = open(file_name, "w")
470                         for line in lines:
471                                 f.write("%s\n" % line)
472                         f.close()
473
474         def _create_world(self, world, world_sets):
475                 #Create /var/lib/portage/world
476                 var_lib_portage = os.path.join(self.eroot, "var", "lib", "portage")
477                 os.makedirs(var_lib_portage)
478
479                 world_file = os.path.join(var_lib_portage, "world")
480                 world_set_file = os.path.join(var_lib_portage, "world_sets")
481
482                 f = open(world_file, "w")
483                 for atom in world:
484                         f.write("%s\n" % atom)
485                 f.close()
486
487                 f = open(world_set_file, "w")
488                 for atom in world_sets:
489                         f.write("%s\n" % atom)
490                 f.close()
491
492         def _load_config(self):
493                 portdir_overlay = []
494                 for repo_name in sorted(self.repo_dirs):
495                         path = self.repo_dirs[repo_name]
496                         if path != self.portdir:
497                                 portdir_overlay.append(path)
498
499                 env = {
500                         "ACCEPT_KEYWORDS": "x86",
501                         "DISTDIR" : self.distdir,
502                         "PKGDIR": self.pkgdir,
503                         "PORTDIR": self.portdir,
504                         "PORTDIR_OVERLAY": " ".join(portdir_overlay),
505                         'PORTAGE_TMPDIR'       : os.path.join(self.eroot, 'var/tmp'),
506                 }
507
508                 if os.environ.get("NOCOLOR"):
509                         env["NOCOLOR"] = os.environ["NOCOLOR"]
510
511                 if os.environ.get("SANDBOX_ON") == "1":
512                         # avoid problems from nested sandbox instances
513                         env["FEATURES"] = "-sandbox"
514
515                 # Pass along PORTAGE_USERNAME and PORTAGE_GRPNAME since they
516                 # need to be inherited by ebuild subprocesses.
517                 if 'PORTAGE_USERNAME' in os.environ:
518                         env['PORTAGE_USERNAME'] = os.environ['PORTAGE_USERNAME']
519                 if 'PORTAGE_GRPNAME' in os.environ:
520                         env['PORTAGE_GRPNAME'] = os.environ['PORTAGE_GRPNAME']
521
522                 trees = portage.create_trees(env=env, eprefix=self.eprefix)
523                 for root, root_trees in trees.items():
524                         settings = root_trees["vartree"].settings
525                         settings._init_dirs()
526                         setconfig = load_default_config(settings, root_trees)
527                         root_trees["root_config"] = RootConfig(settings, root_trees, setconfig)
528                 
529                 return settings, trees
530
531         def run(self, atoms, options={}, action=None):
532                 options = options.copy()
533                 options["--pretend"] = True
534                 if self.debug:
535                         options["--debug"] = True
536
537                 if action is None:
538                         if options.get("--depclean"):
539                                 action = "depclean"
540                         elif options.get("--prune"):
541                                 action = "prune"
542
543                 if "--usepkgonly" in options:
544                         options["--usepkg"] = True
545
546                 global_noiselimit = portage.util.noiselimit
547                 global_emergelog_disable = _emerge.emergelog._disable
548                 try:
549
550                         if not self.debug:
551                                 portage.util.noiselimit = -2
552                         _emerge.emergelog._disable = True
553
554                         if action in ("depclean", "prune"):
555                                 rval, cleanlist, ordered, req_pkg_count = \
556                                         calc_depclean(self.settings, self.trees, None,
557                                         options, action, InternalPackageSet(initial_atoms=atoms, allow_wildcard=True), None)
558                                 result = ResolverPlaygroundDepcleanResult( \
559                                         atoms, rval, cleanlist, ordered, req_pkg_count)
560                         else:
561                                 params = create_depgraph_params(options, action)
562                                 success, depgraph, favorites = backtrack_depgraph(
563                                         self.settings, self.trees, options, params, action, atoms, None)
564                                 depgraph._show_merge_list()
565                                 depgraph.display_problems()
566                                 result = ResolverPlaygroundResult(atoms, success, depgraph, favorites)
567                 finally:
568                         portage.util.noiselimit = global_noiselimit
569                         _emerge.emergelog._disable = global_emergelog_disable
570
571                 return result
572
573         def run_TestCase(self, test_case):
574                 if not isinstance(test_case, ResolverPlaygroundTestCase):
575                         raise TypeError("ResolverPlayground needs a ResolverPlaygroundTestCase")
576                 for atoms in test_case.requests:
577                         result = self.run(atoms, test_case.options, test_case.action)
578                         if not test_case.compare_with_result(result):
579                                 return
580
581         def cleanup(self):
582                 portdb = self.trees[self.eroot]["porttree"].dbapi
583                 portdb.close_caches()
584                 portage.dbapi.porttree.portdbapi.portdbapi_instances.remove(portdb)
585                 if self.debug:
586                         print("\nEROOT=%s" % self.eroot)
587                 else:
588                         shutil.rmtree(self.eroot)
589
590 class ResolverPlaygroundTestCase(object):
591
592         def __init__(self, request, **kwargs):
593                 self.all_permutations = kwargs.pop("all_permutations", False)
594                 self.ignore_mergelist_order = kwargs.pop("ignore_mergelist_order", False)
595                 self.ambiguous_merge_order = kwargs.pop("ambiguous_merge_order", False)
596                 self.check_repo_names = kwargs.pop("check_repo_names", False)
597                 self.merge_order_assertions = kwargs.pop("merge_order_assertions", False)
598
599                 if self.all_permutations:
600                         self.requests = list(permutations(request))
601                 else:
602                         self.requests = [request]
603
604                 self.options = kwargs.pop("options", {})
605                 self.action = kwargs.pop("action", None)
606                 self.test_success = True
607                 self.fail_msg = None
608                 self._checks = kwargs.copy()
609
610         def compare_with_result(self, result):
611                 checks = dict.fromkeys(result.checks)
612                 for key, value in self._checks.items():
613                         if not key in checks:
614                                 raise KeyError("Not an available check: '%s'" % key)
615                         checks[key] = value
616
617                 fail_msgs = []
618                 for key, value in checks.items():
619                         got = getattr(result, key)
620                         expected = value
621
622                         if key in result.optional_checks and expected is None:
623                                 continue
624
625                         if key == "mergelist":
626                                 if not self.check_repo_names:
627                                         #Strip repo names if we don't check them
628                                         if got:
629                                                 new_got = []
630                                                 for cpv in got:
631                                                         if cpv[:1] == "!":
632                                                                 new_got.append(cpv)
633                                                                 continue
634                                                         new_got.append(cpv.split(_repo_separator)[0])
635                                                 got = new_got
636                                         if expected:
637                                                 new_expected = []
638                                                 for obj in expected:
639                                                         if isinstance(obj, basestring):
640                                                                 if obj[:1] == "!":
641                                                                         new_expected.append(obj)
642                                                                         continue
643                                                                 new_expected.append(
644                                                                         obj.split(_repo_separator)[0])
645                                                                 continue
646                                                         new_expected.append(set())
647                                                         for cpv in obj:
648                                                                 if cpv[:1] != "!":
649                                                                         cpv = cpv.split(_repo_separator)[0]
650                                                                 new_expected[-1].add(cpv)
651                                                 expected = new_expected
652                                 if self.ignore_mergelist_order and got is not None:
653                                         got = set(got)
654                                         expected = set(expected)
655
656                                 if self.ambiguous_merge_order and got:
657                                         expected_stack = list(reversed(expected))
658                                         got_stack = list(reversed(got))
659                                         new_expected = []
660                                         match = True
661                                         while got_stack and expected_stack:
662                                                 got_token = got_stack.pop()
663                                                 expected_obj = expected_stack.pop()
664                                                 if isinstance(expected_obj, basestring):
665                                                         new_expected.append(expected_obj)
666                                                         if got_token == expected_obj:
667                                                                 continue
668                                                         # result doesn't match, so stop early
669                                                         match = False
670                                                         break
671                                                 expected_obj = set(expected_obj)
672                                                 try:
673                                                         expected_obj.remove(got_token)
674                                                 except KeyError:
675                                                         # result doesn't match, so stop early
676                                                         match = False
677                                                         break
678                                                 new_expected.append(got_token)
679                                                 while got_stack and expected_obj:
680                                                         got_token = got_stack.pop()
681                                                         try:
682                                                                 expected_obj.remove(got_token)
683                                                         except KeyError:
684                                                                 match = False
685                                                                 break
686                                                         new_expected.append(got_token)
687                                                 if not match:
688                                                         # result doesn't match, so stop early
689                                                         break
690                                                 if expected_obj:
691                                                         # result does not match, so stop early
692                                                         match = False
693                                                         new_expected.append(tuple(expected_obj))
694                                                         break
695                                         if expected_stack:
696                                                 # result does not match, add leftovers to new_expected
697                                                 match = False
698                                                 expected_stack.reverse()
699                                                 new_expected.extend(expected_stack)
700                                         expected = new_expected
701
702                                         if match and self.merge_order_assertions:
703                                                 for node1, node2 in self.merge_order_assertions:
704                                                         if not (got.index(node1) < got.index(node2)):
705                                                                 fail_msgs.append("atoms: (" + \
706                                                                         ", ".join(result.atoms) + "), key: " + \
707                                                                         ("merge_order_assertions, expected: %s" % \
708                                                                         str((node1, node2))) + \
709                                                                         ", got: " + str(got))
710
711                         elif key in ("unstable_keywords", "needed_p_mask_changes") and expected is not None:
712                                 expected = set(expected)
713
714                         if got != expected:
715                                 fail_msgs.append("atoms: (" + ", ".join(result.atoms) + "), key: " + \
716                                         key + ", expected: " + str(expected) + ", got: " + str(got))
717                 if fail_msgs:
718                         self.test_success = False
719                         self.fail_msg = "\n".join(fail_msgs)
720                         return False
721                 return True
722
723 class ResolverPlaygroundResult(object):
724
725         checks = (
726                 "success", "mergelist", "use_changes", "license_changes", "unstable_keywords", "slot_collision_solutions",
727                 "circular_dependency_solutions", "needed_p_mask_changes",
728                 )
729         optional_checks = (
730                 )
731
732         def __init__(self, atoms, success, mydepgraph, favorites):
733                 self.atoms = atoms
734                 self.success = success
735                 self.depgraph = mydepgraph
736                 self.favorites = favorites
737                 self.mergelist = None
738                 self.use_changes = None
739                 self.license_changes = None
740                 self.unstable_keywords = None
741                 self.needed_p_mask_changes = None
742                 self.slot_collision_solutions = None
743                 self.circular_dependency_solutions = None
744
745                 if self.depgraph._dynamic_config._serialized_tasks_cache is not None:
746                         self.mergelist = []
747                         for x in self.depgraph._dynamic_config._serialized_tasks_cache:
748                                 if isinstance(x, Blocker):
749                                         self.mergelist.append(x.atom)
750                                 else:
751                                         repo_str = ""
752                                         if x.metadata["repository"] != "test_repo":
753                                                 repo_str = _repo_separator + x.metadata["repository"]
754                                         mergelist_str = x.cpv + repo_str
755                                         if x.built:
756                                                 if x.operation == "merge":
757                                                         desc = x.type_name
758                                                 else:
759                                                         desc = x.operation
760                                                 mergelist_str = "[%s]%s" % (desc, mergelist_str)
761                                         self.mergelist.append(mergelist_str)
762
763                 if self.depgraph._dynamic_config._needed_use_config_changes:
764                         self.use_changes = {}
765                         for pkg, needed_use_config_changes in \
766                                 self.depgraph._dynamic_config._needed_use_config_changes.items():
767                                 new_use, changes = needed_use_config_changes
768                                 self.use_changes[pkg.cpv] = changes
769
770                 if self.depgraph._dynamic_config._needed_unstable_keywords:
771                         self.unstable_keywords = set()
772                         for pkg in self.depgraph._dynamic_config._needed_unstable_keywords:
773                                 self.unstable_keywords.add(pkg.cpv)
774
775                 if self.depgraph._dynamic_config._needed_p_mask_changes:
776                         self.needed_p_mask_changes = set()
777                         for pkg in self.depgraph._dynamic_config._needed_p_mask_changes:
778                                 self.needed_p_mask_changes.add(pkg.cpv)
779
780                 if self.depgraph._dynamic_config._needed_license_changes:
781                         self.license_changes = {}
782                         for pkg, missing_licenses in self.depgraph._dynamic_config._needed_license_changes.items():
783                                 self.license_changes[pkg.cpv] = missing_licenses
784
785                 if self.depgraph._dynamic_config._slot_conflict_handler is not None:
786                         self.slot_collision_solutions  = []
787                         handler = self.depgraph._dynamic_config._slot_conflict_handler
788
789                         for change in handler.changes:
790                                 new_change = {}
791                                 for pkg in change:
792                                         new_change[pkg.cpv] = change[pkg]
793                                 self.slot_collision_solutions.append(new_change)
794
795                 if self.depgraph._dynamic_config._circular_dependency_handler is not None:
796                         handler = self.depgraph._dynamic_config._circular_dependency_handler
797                         sol = handler.solutions
798                         self.circular_dependency_solutions = dict( zip([x.cpv for x in sol.keys()], sol.values()) )
799
800 class ResolverPlaygroundDepcleanResult(object):
801
802         checks = (
803                 "success", "cleanlist", "ordered", "req_pkg_count",
804                 )
805         optional_checks = (
806                 "ordered", "req_pkg_count",
807                 )
808
809         def __init__(self, atoms, rval, cleanlist, ordered, req_pkg_count):
810                 self.atoms = atoms
811                 self.success = rval == 0
812                 self.cleanlist = cleanlist
813                 self.ordered = ordered
814                 self.req_pkg_count = req_pkg_count