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