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