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