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