# Copyright 2011-2023 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

from portage.tests import TestCase
from portage.tests.resolver.ResolverPlayground import (
    ResolverPlayground,
    ResolverPlaygroundTestCase,
)


class MergeOrderTestCase(TestCase):
    def testMergeOrder(self):
        ebuilds = {
            "app-misc/blocker-buildtime-a-1": {},
            "app-misc/blocker-buildtime-unbuilt-a-1": {
                "DEPEND": "!app-misc/installed-blocker-a",
            },
            "app-misc/blocker-buildtime-unbuilt-hard-a-1": {
                "EAPI": "2",
                "DEPEND": "!!app-misc/installed-blocker-a",
            },
            "app-misc/blocker-update-order-a-1": {},
            "app-misc/blocker-update-order-hard-a-1": {},
            "app-misc/blocker-update-order-hard-unsolvable-a-1": {},
            "app-misc/blocker-runtime-a-1": {},
            "app-misc/blocker-runtime-b-1": {},
            "app-misc/blocker-runtime-hard-a-1": {},
            "app-misc/circ-buildtime-a-0": {},
            "app-misc/circ-buildtime-a-1": {
                "RDEPEND": "app-misc/circ-buildtime-b",
            },
            "app-misc/circ-buildtime-b-1": {
                "RDEPEND": "app-misc/circ-buildtime-c",
            },
            "app-misc/circ-buildtime-c-1": {
                "DEPEND": "app-misc/circ-buildtime-a",
            },
            "app-misc/circ-buildtime-unsolvable-a-1": {
                "RDEPEND": "app-misc/circ-buildtime-unsolvable-b",
            },
            "app-misc/circ-buildtime-unsolvable-b-1": {
                "RDEPEND": "app-misc/circ-buildtime-unsolvable-c",
            },
            "app-misc/circ-buildtime-unsolvable-c-1": {
                "DEPEND": "app-misc/circ-buildtime-unsolvable-a",
            },
            "app-misc/circ-post-runtime-a-1": {
                "PDEPEND": "app-misc/circ-post-runtime-b",
            },
            "app-misc/circ-post-runtime-b-1": {
                "RDEPEND": "app-misc/circ-post-runtime-c",
            },
            "app-misc/circ-post-runtime-c-1": {
                "RDEPEND": "app-misc/circ-post-runtime-a",
            },
            "app-misc/circ-runtime-a-1": {
                "RDEPEND": "app-misc/circ-runtime-b",
            },
            "app-misc/circ-runtime-b-1": {
                "RDEPEND": "app-misc/circ-runtime-c",
            },
            "app-misc/circ-runtime-c-1": {
                "RDEPEND": "app-misc/circ-runtime-a",
            },
            "app-misc/circ-satisfied-a-0": {
                "RDEPEND": "app-misc/circ-satisfied-b",
            },
            "app-misc/circ-satisfied-a-1": {
                "RDEPEND": "app-misc/circ-satisfied-b",
            },
            "app-misc/circ-satisfied-b-0": {
                "RDEPEND": "app-misc/circ-satisfied-c",
            },
            "app-misc/circ-satisfied-b-1": {
                "RDEPEND": "app-misc/circ-satisfied-c",
            },
            "app-misc/circ-satisfied-c-0": {
                "DEPEND": "app-misc/circ-satisfied-a",
                "RDEPEND": "app-misc/circ-satisfied-a",
            },
            "app-misc/circ-satisfied-c-1": {
                "DEPEND": "app-misc/circ-satisfied-a",
                "RDEPEND": "app-misc/circ-satisfied-a",
            },
            "app-misc/circ-direct-a-1": {
                "RDEPEND": "app-misc/circ-direct-b",
            },
            "app-misc/circ-direct-b-1": {
                "RDEPEND": "app-misc/circ-direct-a",
                "DEPEND": "app-misc/circ-direct-a",
            },
            "app-misc/circ-smallest-a-1": {
                "RDEPEND": "app-misc/circ-smallest-b",
            },
            "app-misc/circ-smallest-b-1": {
                "RDEPEND": "app-misc/circ-smallest-a",
            },
            "app-misc/circ-smallest-c-1": {
                "RDEPEND": "app-misc/circ-smallest-d",
            },
            "app-misc/circ-smallest-d-1": {
                "RDEPEND": "app-misc/circ-smallest-e",
            },
            "app-misc/circ-smallest-e-1": {
                "RDEPEND": "app-misc/circ-smallest-c",
            },
            "app-misc/circ-smallest-f-1": {
                "RDEPEND": "app-misc/circ-smallest-g app-misc/circ-smallest-a app-misc/circ-smallest-c",
            },
            "app-misc/circ-smallest-g-1": {
                "RDEPEND": "app-misc/circ-smallest-f",
            },
            "app-misc/installed-blocker-a-1": {
                "EAPI": "2",
                "DEPEND": "!app-misc/blocker-buildtime-a",
                "RDEPEND": "!app-misc/blocker-runtime-a !app-misc/blocker-runtime-b !!app-misc/blocker-runtime-hard-a",
            },
            "app-misc/installed-old-version-blocks-a-1": {
                "RDEPEND": "!app-misc/blocker-update-order-a",
            },
            "app-misc/installed-old-version-blocks-a-2": {},
            "app-misc/installed-old-version-blocks-hard-a-1": {
                "EAPI": "2",
                "RDEPEND": "!!app-misc/blocker-update-order-hard-a",
            },
            "app-misc/installed-old-version-blocks-hard-a-2": {},
            "app-misc/installed-old-version-blocks-hard-unsolvable-a-1": {
                "EAPI": "2",
                "RDEPEND": "!!app-misc/blocker-update-order-hard-unsolvable-a",
            },
            "app-misc/installed-old-version-blocks-hard-unsolvable-a-2": {
                "DEPEND": "app-misc/blocker-update-order-hard-unsolvable-a",
                "RDEPEND": "",
            },
            "app-misc/some-app-a-1": {
                "RDEPEND": "app-misc/circ-runtime-a app-misc/circ-runtime-b",
            },
            "app-misc/some-app-b-1": {
                "RDEPEND": "app-misc/circ-post-runtime-a app-misc/circ-post-runtime-b",
            },
            "app-misc/some-app-c-1": {
                "RDEPEND": "app-misc/circ-buildtime-a app-misc/circ-buildtime-b",
            },
            "app-admin/eselect-python-20100321": {},
            "sys-apps/portage-2.1.9.42": {
                "DEPEND": "dev-lang/python",
                "RDEPEND": "dev-lang/python",
            },
            "sys-apps/portage-2.1.9.49": {
                "DEPEND": "dev-lang/python >=app-admin/eselect-python-20091230",
                "RDEPEND": "dev-lang/python",
            },
            "dev-lang/python-3.1": {},
            "dev-lang/python-3.2": {},
            "virtual/libc-0": {
                "RDEPEND": "sys-libs/glibc",
            },
            "sys-devel/gcc-4.5.2": {},
            "sys-devel/binutils-2.18": {},
            "sys-devel/binutils-2.20.1": {},
            "sys-libs/glibc-2.11": {
                "DEPEND": "virtual/os-headers sys-devel/gcc sys-devel/binutils",
                "RDEPEND": "",
            },
            "sys-libs/glibc-2.13": {
                "DEPEND": "virtual/os-headers sys-devel/gcc sys-devel/binutils",
                "RDEPEND": "",
            },
            "virtual/os-headers-0": {
                "RDEPEND": "sys-kernel/linux-headers",
            },
            "sys-kernel/linux-headers-2.6.38": {
                "DEPEND": "app-arch/xz-utils",
                "RDEPEND": "",
            },
            "sys-kernel/linux-headers-2.6.39": {
                "DEPEND": "app-arch/xz-utils",
                "RDEPEND": "",
            },
            "app-arch/xz-utils-5.0.1": {},
            "app-arch/xz-utils-5.0.2": {},
            "dev-util/pkgconfig-0.25-r2": {},
            "kde-base/kdelibs-3.5.7": {
                "PDEPEND": "kde-misc/kdnssd-avahi",
            },
            "kde-misc/kdnssd-avahi-0.1.2": {
                "DEPEND": "kde-base/kdelibs app-arch/xz-utils dev-util/pkgconfig",
                "RDEPEND": "kde-base/kdelibs",
            },
            "kde-base/kdnssd-3.5.7": {
                "DEPEND": "kde-base/kdelibs",
                "RDEPEND": "kde-base/kdelibs",
            },
            "kde-base/libkdegames-3.5.7": {
                "DEPEND": "kde-base/kdelibs",
                "RDEPEND": "kde-base/kdelibs",
            },
            "kde-base/kmines-3.5.7": {
                "DEPEND": "kde-base/libkdegames",
                "RDEPEND": "kde-base/libkdegames",
            },
            "media-libs/mesa-9.1.3": {
                "EAPI": "5",
                "IUSE": "+xorg",
                "DEPEND": "xorg? ( x11-base/xorg-server:= )",
                "RDEPEND": "xorg? ( x11-base/xorg-server:= )",
            },
            "media-video/libav-0.7_pre20110327": {
                "EAPI": "2",
                "IUSE": "X +encode",
                "RDEPEND": "!media-video/ffmpeg",
            },
            "media-video/ffmpeg-0.7_rc1": {
                "EAPI": "2",
                "IUSE": "X +encode",
            },
            "virtual/ffmpeg-0.6.90": {
                "EAPI": "2",
                "IUSE": "X +encode",
                "RDEPEND": "|| ( >=media-video/ffmpeg-0.6.90_rc0-r2[X=,encode=] >=media-video/libav-0.6.90_rc[X=,encode=] )",
            },
            "x11-base/xorg-drivers-1.20-r2": {
                "EAPI": "7",
                "IUSE": "+video_cards_fbdev",
                "PDEPEND": "x11-base/xorg-server video_cards_fbdev? ( x11-drivers/xf86-video-fbdev )",
            },
            "x11-base/xorg-server-1.14.1": {
                "EAPI": "5",
                "SLOT": "0/1.14.1",
                "DEPEND": "media-libs/mesa",
                "RDEPEND": "media-libs/mesa",
                "PDEPEND": "x11-base/xorg-drivers",
            },
            "x11-drivers/xf86-video-fbdev-0.5.0-r1": {
                "EAPI": "7",
                "DEPEND": "x11-base/xorg-server",
                "RDEPEND": "x11-base/xorg-server:=",
            },
        }

        installed = {
            "app-misc/circ-direct-a-1": {
                "RDEPEND": "app-misc/circ-direct-b",
            },
            "app-misc/circ-direct-b-1": {
                "RDEPEND": "app-misc/circ-direct-a",
                "DEPEND": "app-misc/circ-direct-a",
            },
            "app-misc/circ-buildtime-a-0": {},
            "app-misc/circ-satisfied-a-0": {
                "RDEPEND": "app-misc/circ-satisfied-b",
            },
            "app-misc/circ-satisfied-b-0": {
                "RDEPEND": "app-misc/circ-satisfied-c",
            },
            "app-misc/circ-satisfied-c-0": {
                "DEPEND": "app-misc/circ-satisfied-a",
                "RDEPEND": "app-misc/circ-satisfied-a",
            },
            "app-misc/installed-blocker-a-1": {
                "EAPI": "2",
                "DEPEND": "!app-misc/blocker-buildtime-a",
                "RDEPEND": "!app-misc/blocker-runtime-a !app-misc/blocker-runtime-b !!app-misc/blocker-runtime-hard-a",
            },
            "app-misc/installed-old-version-blocks-a-1": {
                "RDEPEND": "!app-misc/blocker-update-order-a",
            },
            "app-misc/installed-old-version-blocks-hard-a-1": {
                "EAPI": "2",
                "RDEPEND": "!!app-misc/blocker-update-order-hard-a",
            },
            "app-misc/installed-old-version-blocks-hard-unsolvable-a-1": {
                "EAPI": "2",
                "RDEPEND": "!!app-misc/blocker-update-order-hard-unsolvable-a",
            },
            "sys-apps/portage-2.1.9.42": {
                "DEPEND": "dev-lang/python",
                "RDEPEND": "dev-lang/python",
            },
            "dev-lang/python-3.1": {},
            "virtual/libc-0": {
                "RDEPEND": "sys-libs/glibc",
            },
            "sys-devel/binutils-2.18": {},
            "sys-libs/glibc-2.11": {
                "DEPEND": "virtual/os-headers sys-devel/gcc sys-devel/binutils",
                "RDEPEND": "",
            },
            "virtual/os-headers-0": {
                "RDEPEND": "sys-kernel/linux-headers",
            },
            "sys-kernel/linux-headers-2.6.38": {
                "DEPEND": "app-arch/xz-utils",
                "RDEPEND": "",
            },
            "app-arch/xz-utils-5.0.1": {},
            "media-libs/mesa-9.1.3": {
                "EAPI": "5",
                "IUSE": "+xorg",
                "USE": "xorg",
                "DEPEND": "x11-base/xorg-server:0/1.14.1=",
                "RDEPEND": "x11-base/xorg-server:0/1.14.1=",
            },
            "media-video/ffmpeg-0.7_rc1": {
                "EAPI": "2",
                "IUSE": "X +encode",
                "USE": "encode",
            },
            "virtual/ffmpeg-0.6.90": {
                "EAPI": "2",
                "IUSE": "X +encode",
                "USE": "encode",
                "RDEPEND": "|| ( >=media-video/ffmpeg-0.6.90_rc0-r2[X=,encode=] >=media-video/libav-0.6.90_rc[X=,encode=] )",
            },
            "x11-base/xorg-drivers-1.20-r2": {
                "EAPI": "7",
                "IUSE": "+video_cards_fbdev",
                "USE": "video_cards_fbdev",
                "PDEPEND": "x11-base/xorg-server x11-drivers/xf86-video-fbdev",
            },
            "x11-base/xorg-server-1.14.1": {
                "EAPI": "5",
                "SLOT": "0/1.14.1",
                "DEPEND": "media-libs/mesa",
                "RDEPEND": "media-libs/mesa",
                "PDEPEND": "x11-base/xorg-drivers",
            },
            "x11-drivers/xf86-video-fbdev-0.5.0-r1": {
                "EAPI": "7",
                "DEPEND": "x11-base/xorg-server",
                "RDEPEND": "x11-base/xorg-server:0/1.14.1=",
            },
        }

        test_cases = (
            ResolverPlaygroundTestCase(
                ["app-misc/circ-direct-a", "app-misc/circ-direct-b"],
                success=True,
                all_permutations=True,
                mergelist=["app-misc/circ-direct-a-1", "app-misc/circ-direct-b-1"],
            ),
            ResolverPlaygroundTestCase(
                ["app-misc/some-app-a"],
                success=True,
                ambiguous_merge_order=True,
                mergelist=[
                    (
                        "app-misc/circ-runtime-a-1",
                        "app-misc/circ-runtime-b-1",
                        "app-misc/circ-runtime-c-1",
                    ),
                    "app-misc/some-app-a-1",
                ],
            ),
            ResolverPlaygroundTestCase(
                ["app-misc/some-app-a"],
                success=True,
                ambiguous_merge_order=True,
                mergelist=[
                    (
                        "app-misc/circ-runtime-c-1",
                        "app-misc/circ-runtime-b-1",
                        "app-misc/circ-runtime-a-1",
                    ),
                    "app-misc/some-app-a-1",
                ],
            ),
            # Test unsolvable circular dep that is RDEPEND in one
            # direction and DEPEND in the other.
            ResolverPlaygroundTestCase(
                ["app-misc/circ-buildtime-unsolvable-a"],
                success=False,
                circular_dependency_solutions={},
            ),
            # Test optimal merge order for a circular dep that is
            # RDEPEND in one direction and DEPEND in the other.
            # This requires an installed instance of the DEPEND
            # package in order to be solvable.
            ResolverPlaygroundTestCase(
                ["app-misc/some-app-c", "app-misc/circ-buildtime-a"],
                success=True,
                ambiguous_merge_order=True,
                # The following merge order assertion reflects optimal order for
                # a circular relationship which is DEPEND in one direction and
                # RDEPEND in the other. However, it is not respected because
                # it would result in a temporarily broken RDEPEND, so we instead
                # rely on satisfied installed build-time dependencies.
                merge_order_assertions=(
                    ("app-misc/circ-buildtime-a-1", "app-misc/circ-buildtime-c-1"),
                ),
                mergelist=[
                    (
                        "app-misc/circ-buildtime-b-1",
                        "app-misc/circ-buildtime-c-1",
                        "app-misc/circ-buildtime-a-1",
                    ),
                    "app-misc/some-app-c-1",
                ],
            ),
            # Test optimal merge order for a circular dep that is
            # RDEPEND in one direction and PDEPEND in the other.
            ResolverPlaygroundTestCase(
                ["app-misc/some-app-b"],
                success=True,
                ambiguous_merge_order=True,
                mergelist=[
                    "app-misc/circ-post-runtime-a-1",
                    (
                        "app-misc/circ-post-runtime-b-1",
                        "app-misc/circ-post-runtime-c-1",
                    ),
                    "app-misc/some-app-b-1",
                ],
            ),
            # Test optimal merge order for a circular dep that is
            # RDEPEND in one direction and DEPEND in the other,
            # with all dependencies initially satisfied. Optimally,
            # the DEPEND/buildtime dep should be updated before the
            # package that depends on it, even though it's feasible
            # to update it later since it is already satisfied.
            ResolverPlaygroundTestCase(
                [
                    "app-misc/circ-satisfied-a",
                    "app-misc/circ-satisfied-b",
                    "app-misc/circ-satisfied-c",
                ],
                success=True,
                all_permutations=True,
                ambiguous_merge_order=True,
                merge_order_assertions=(
                    ("app-misc/circ-satisfied-a-1", "app-misc/circ-satisfied-c-1"),
                ),
                mergelist=[
                    (
                        "app-misc/circ-satisfied-a-1",
                        "app-misc/circ-satisfied-b-1",
                        "app-misc/circ-satisfied-c-1",
                    )
                ],
            ),
            # In the case of multiple runtime cycles, where some cycles
            # may depend on smaller independent cycles, it's optimal
            # to merge smaller independent cycles before other cycles
            # that depend on them.
            ResolverPlaygroundTestCase(
                [
                    "app-misc/circ-smallest-a",
                    "app-misc/circ-smallest-c",
                    "app-misc/circ-smallest-f",
                ],
                success=True,
                ambiguous_merge_order=True,
                all_permutations=True,
                mergelist=[
                    ("app-misc/circ-smallest-a-1", "app-misc/circ-smallest-b-1"),
                    (
                        "app-misc/circ-smallest-c-1",
                        "app-misc/circ-smallest-d-1",
                        "app-misc/circ-smallest-e-1",
                    ),
                    ("app-misc/circ-smallest-f-1", "app-misc/circ-smallest-g-1"),
                ],
            ),
            # installed package has buildtime-only blocker
            # that should be ignored
            ResolverPlaygroundTestCase(
                ["app-misc/blocker-buildtime-a"],
                success=True,
                mergelist=["app-misc/blocker-buildtime-a-1"],
            ),
            # We're installing a package that an old version of
            # an installed package blocks. However, an update is
            # available to the old package. The old package should
            # be updated first, in order to solve the blocker without
            # any need for blocking packages to temporarily overlap.
            ResolverPlaygroundTestCase(
                [
                    "app-misc/blocker-update-order-a",
                    "app-misc/installed-old-version-blocks-a",
                ],
                success=True,
                all_permutations=True,
                mergelist=[
                    "app-misc/installed-old-version-blocks-a-2",
                    "app-misc/blocker-update-order-a-1",
                ],
            ),
            # This is the same as above but with a hard blocker. The hard
            # blocker is solved automatically since the update makes it
            # irrelevant.
            ResolverPlaygroundTestCase(
                [
                    "app-misc/blocker-update-order-hard-a",
                    "app-misc/installed-old-version-blocks-hard-a",
                ],
                success=True,
                all_permutations=True,
                mergelist=[
                    "app-misc/installed-old-version-blocks-hard-a-2",
                    "app-misc/blocker-update-order-hard-a-1",
                ],
            ),
            # This is similar to the above case except that it's unsolvable
            # due to merge order, unless bug 250286 is implemented so that
            # the installed blocker will be unmerged before installation
            # of the package it blocks (rather than after like a soft blocker
            # would be handled). The "unmerge before" behavior requested
            # in bug 250286 must be optional since essential programs or
            # libraries may be temporarily unavailable during a
            # non-overlapping update like this.
            ResolverPlaygroundTestCase(
                [
                    "app-misc/blocker-update-order-hard-unsolvable-a",
                    "app-misc/installed-old-version-blocks-hard-unsolvable-a",
                ],
                success=False,
                all_permutations=True,
                ambiguous_merge_order=True,
                merge_order_assertions=(
                    (
                        "app-misc/blocker-update-order-hard-unsolvable-a-1",
                        "app-misc/installed-old-version-blocks-hard-unsolvable-a-2",
                    ),
                ),
                mergelist=[
                    (
                        "app-misc/blocker-update-order-hard-unsolvable-a-1",
                        "app-misc/installed-old-version-blocks-hard-unsolvable-a-2",
                        "!!app-misc/blocker-update-order-hard-unsolvable-a",
                    )
                ],
            ),
            # The installed package has runtime blockers that
            # should cause it to be uninstalled. The uninstall
            # task is executed only after blocking packages have
            # been merged.
            # TODO: distinguish between install/uninstall tasks in mergelist
            ResolverPlaygroundTestCase(
                ["app-misc/blocker-runtime-a", "app-misc/blocker-runtime-b"],
                success=True,
                all_permutations=True,
                ambiguous_merge_order=True,
                mergelist=[
                    ("app-misc/blocker-runtime-a-1", "app-misc/blocker-runtime-b-1"),
                    "[uninstall]app-misc/installed-blocker-a-1",
                    ("!app-misc/blocker-runtime-a", "!app-misc/blocker-runtime-b"),
                ],
            ),
            # We have a soft buildtime blocker against an installed
            # package that should cause it to be uninstalled. Note that with
            # soft blockers, the blocking packages are allowed to temporarily
            # overlap. This allows any essential programs/libraries provided
            # by both packages to be available at all times.
            # TODO: distinguish between install/uninstall tasks in mergelist
            ResolverPlaygroundTestCase(
                ["app-misc/blocker-buildtime-unbuilt-a"],
                success=True,
                mergelist=[
                    "app-misc/blocker-buildtime-unbuilt-a-1",
                    "[uninstall]app-misc/installed-blocker-a-1",
                    "!app-misc/installed-blocker-a",
                ],
            ),
            # We have a hard buildtime blocker against an installed
            # package that will not resolve automatically (unless
            # the option requested in bug 250286 is implemented).
            ResolverPlaygroundTestCase(
                ["app-misc/blocker-buildtime-unbuilt-hard-a"],
                success=False,
                mergelist=[
                    "app-misc/blocker-buildtime-unbuilt-hard-a-1",
                    "!!app-misc/installed-blocker-a",
                ],
            ),
            # An installed package has a hard runtime blocker that
            # will not resolve automatically (unless the option
            # requested in bug 250286 is implemented).
            ResolverPlaygroundTestCase(
                ["app-misc/blocker-runtime-hard-a"],
                success=False,
                mergelist=[
                    "app-misc/blocker-runtime-hard-a-1",
                    "!!app-misc/blocker-runtime-hard-a",
                ],
            ),
            # Test swapping of providers for a new-style virtual package,
            # which relies on delayed evaluation of disjunctive (virtual
            # and ||) deps as required to solve bug #264434. Note that
            # this behavior is not supported for old-style PROVIDE virtuals,
            # as reported in bug #339164.
            ResolverPlaygroundTestCase(
                ["media-video/libav"],
                success=True,
                mergelist=[
                    "media-video/libav-0.7_pre20110327",
                    "[uninstall]media-video/ffmpeg-0.7_rc1",
                    "!media-video/ffmpeg",
                ],
            ),
            # Test that OS_HEADERS_PACKAGE_ATOM and LIBC_PACKAGE_ATOM
            # are merged asap, in order to account for implicit
            # dependencies. See bug #303567. Optimally, satisfied deps
            # are always merged after the asap nodes that depend on them.
            ResolverPlaygroundTestCase(
                [
                    "app-arch/xz-utils",
                    "sys-kernel/linux-headers",
                    "sys-devel/binutils",
                    "sys-libs/glibc",
                ],
                options={"--complete-graph": True},
                success=True,
                all_permutations=True,
                ambiguous_merge_order=True,
                mergelist=[
                    "sys-kernel/linux-headers-2.6.39",
                    "sys-devel/gcc-4.5.2",
                    "sys-libs/glibc-2.13",
                    ("app-arch/xz-utils-5.0.2", "sys-devel/binutils-2.20.1"),
                ],
            ),
            # Test asap install of PDEPEND for bug #180045.
            ResolverPlaygroundTestCase(
                [
                    "kde-base/kmines",
                    "kde-base/kdnssd",
                    "kde-base/kdelibs",
                    "app-arch/xz-utils",
                ],
                success=True,
                all_permutations=True,
                ambiguous_merge_order=True,
                merge_order_assertions=(
                    ("dev-util/pkgconfig-0.25-r2", "kde-misc/kdnssd-avahi-0.1.2"),
                    ("kde-misc/kdnssd-avahi-0.1.2", "kde-base/libkdegames-3.5.7"),
                    ("kde-misc/kdnssd-avahi-0.1.2", "kde-base/kdnssd-3.5.7"),
                    ("kde-base/libkdegames-3.5.7", "kde-base/kmines-3.5.7"),
                ),
                mergelist=[
                    (
                        "kde-base/kdelibs-3.5.7",
                        "dev-util/pkgconfig-0.25-r2",
                        "kde-misc/kdnssd-avahi-0.1.2",
                        "app-arch/xz-utils-5.0.2",
                        "kde-base/libkdegames-3.5.7",
                        "kde-base/kdnssd-3.5.7",
                        "kde-base/kmines-3.5.7",
                    )
                ],
            ),
            # Test satisfied circular DEPEND/RDEPEND with one := operator.
            # Both deps are already satisfied by installed packages, but
            # the := dep is given higher priority in merge order.
            ResolverPlaygroundTestCase(
                [
                    "media-libs/mesa",
                    "x11-drivers/xf86-video-fbdev",
                    "x11-base/xorg-server",
                ],
                success=True,
                all_permutations=True,
                mergelist=[
                    "x11-base/xorg-server-1.14.1",
                    "media-libs/mesa-9.1.3",
                    "x11-drivers/xf86-video-fbdev-0.5.0-r1",
                ],
            ),
            # Test prioritization of the find_smallest_cycle function, which should
            # minimize the use of installed packages to break cycles. If installed
            # packages must be used to break cycles, then it should prefer to do this
            # for runtime dependencies over buildtime dependencies. If a package needs
            # to be uninstalled in order to solve a blocker, then it should prefer to
            # do this before it uses an installed package to break a cycle.
            ResolverPlaygroundTestCase(
                [
                    "app-misc/some-app-a",
                    "app-misc/some-app-b",
                    "app-misc/some-app-c",
                    "app-misc/circ-buildtime-a",
                    "app-misc/blocker-buildtime-unbuilt-a",
                    "media-libs/mesa",
                    "x11-base/xorg-server",
                    "app-misc/circ-direct-a",
                    "app-misc/circ-direct-b",
                    "app-misc/circ-satisfied-a",
                    "app-misc/circ-satisfied-b",
                    "app-misc/circ-satisfied-c",
                ],
                success=True,
                mergelist=[
                    "app-misc/circ-post-runtime-a-1",
                    "app-misc/circ-post-runtime-c-1",
                    "app-misc/circ-post-runtime-b-1",
                    "app-misc/some-app-b-1",
                    "app-misc/circ-runtime-a-1",
                    "app-misc/circ-runtime-c-1",
                    "app-misc/circ-runtime-b-1",
                    "app-misc/some-app-a-1",
                    "app-misc/blocker-buildtime-unbuilt-a-1",
                    "[uninstall]app-misc/installed-blocker-a-1",
                    "!app-misc/installed-blocker-a",
                    "app-misc/circ-direct-a-1",
                    "app-misc/circ-direct-b-1",
                    "x11-base/xorg-server-1.14.1",
                    "media-libs/mesa-9.1.3",
                    "app-misc/circ-buildtime-a-1",
                    "app-misc/circ-buildtime-c-1",
                    "app-misc/circ-buildtime-b-1",
                    "app-misc/some-app-c-1",
                    "app-misc/circ-satisfied-a-1",
                    "app-misc/circ-satisfied-c-1",
                    "app-misc/circ-satisfied-b-1",
                ],
            ),
        )

        playground = ResolverPlayground(ebuilds=ebuilds, installed=installed)
        try:
            for test_case in test_cases:
                playground.run_TestCase(test_case)
                self.assertEqual(test_case.test_success, True, test_case.fail_msg)
        finally:
            playground.cleanup()
