src/tests/cli/t_pkg_install.py
author saurabh.vyas@oracle.com
Fri, 23 Sep 2016 11:47:00 +0530
branchs11u3-sru
changeset 3432 489e5c945d1d
parent 3431 986e4ec518b8
child 3434 b3c023f9936c
permissions -rw-r--r--
19483340 driver action policy update displays false failure messages

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#

#
# Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
#

import testutils
if __name__ == "__main__":
        testutils.setup_environment("../../../proto")
import pkg5unittest

import errno
import os
import platform
import re
import shutil
import socket
import subprocess
import stat
import tempfile
import time
import unittest
import urllib2

import pkg.actions
import pkg.digest as digest
import pkg.fmri as fmri
import pkg.manifest as manifest
import pkg.portable as portable

from pkg.client.pkgdefs import EXIT_OOPS

try:
        import pkg.sha512
        sha512_supported = True
except ImportError:
        sha512_supported = False

class _TestHelper(object):
        """Private helper class for shared functionality between test
        classes."""

        def _assertEditables(self, moved=[], removed=[], installed=[],
            updated=[]):
                """Private helper function that verifies that expected editables
                are listed in parsable output.  If no editable of a given type
                is specified, then no editable files are expected."""

                changed = []
                if moved:
                        changed.append(['moved', moved])
                if removed:
                        changed.append(['removed', removed])
                if installed:
                        changed.append(['installed', installed])
                if updated:
                        changed.append(['updated', updated])

                self.assertEqualParsable(self.output,
                        include=["change-editables"],
                        change_editables=changed)


class TestPkgInstallBasics(pkg5unittest.SingleDepotTestCase):
        # Only start/stop the depot once (instead of for every test)
        persistent_setup = True

        foo10 = """
            open [email protected],5.11-0
            close """

        foo11 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=/lib
            add file tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.so.1 timestamp="20080731T024051Z"
            close """
        foo11_timestamp = 1217472051

        foo12 = """
            open [email protected],5.11-0
            add file tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.so.1
            close """

        afoo10 = """
            open a/[email protected],5.11-0
            close """

        boring10 = """
            open [email protected],5.11-0
            close """

        boring11 = """
            open [email protected],5.11-0
            close """

        bar10 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cat
            close """

        bar11 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cat
            close """

        xfoo10 = """
            open [email protected],5.11-0
            close """

        xbar10 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cat
            close """

        xbar11 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cat
            close """


        bar12 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cat
            close """

        baz10 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/baz mode=0555 owner=root group=bin path=/bin/baz
            close """

        deep10 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cat
            close """

        xdeep10 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cat
            close """

        ydeep10 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cat
            close """

        a6018_1 = """
            open [email protected],5.11-0
            close """

        a6018_2 = """
            open [email protected],5.11-0
            close """

        b6018_1 = """
            open [email protected],5.11-0
            add depend type=optional fmri=a6018@1
            close """

        badfile10 = """
            open [email protected],5.11-0
            add file tmp/baz mode=644 owner=root group=bin path=/tmp/baz-file
            close """

        baddir10 = """
            open [email protected],5.11-0
            add dir mode=755 owner=root group=bin path=/tmp/baz-dir
            close """

        a16189 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            close """

        b16189 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cat
            close """

        fuzzy = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path="opt/dir with white\tspace"
            add file tmp/cat mode=0644 owner=root group=bin path="opt/dir with white\tspace/cat in a hat"
            close
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path="opt/dir with white\tspace"
            add file tmp/cat mode=0644 owner=root group=bin path="opt/dir with white\tspace/cat in a hat"
            add link path=etc/cat_link target="../opt/dir with white\tspace/cat in a hat"
            close """

        ffoo10 = """
            open [email protected],5.11-0
            add file tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.so.1
            close """

        ffoo11 = """
            open [email protected],5.11-0
            add file tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.so.1
            close """

        fbar10 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cat
            close """

        fbar11 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            add dir mode=0755 owner=root group=bin path=/bin
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cät
            close """

        secret1 = """
            open [email protected]
            add dir mode=0755 owner=root group=bin path=/p1
            add file tmp/cat mode=0555 owner=root group=bin sysattr=TH path=/p1/cat
            close """

        secret2 = """
            open [email protected]
            add dir mode=0755 owner=root group=bin path=/p2
            add file tmp/cat mode=0555 owner=root group=bin sysattr=hidden,sensitive path=/p2/cat
            close """

        secret3 = """
            open [email protected]
            add dir mode=0755 owner=root group=bin path=/p3
            add file tmp/cat mode=0555 owner=root group=bin sysattr=horst path=/p3/cat
            close """

        secret4 = """
            open [email protected]
            add dir mode=0755 owner=root group=bin path=/p3
            add file tmp/cat mode=0555 owner=root group=bin sysattr=hidden,horst path=/p3/cat
            close """

        misc_files = [ "tmp/libc.so.1", "tmp/cat", "tmp/baz" ]

        def setUp(self):
                pkg5unittest.SingleDepotTestCase.setUp(self)
                self.make_misc_files(self.misc_files)

        def test_cli(self):
                """Test bad cli options"""

                self.cli_helper("install")
                self.cli_helper("exact-install")

        def cli_helper(self, install_cmd):
                self.image_create(self.rurl)

                self.pkg("-@", exit=2)
                self.pkg("-s status", exit=2)
                self.pkg("-R status", exit=2)

                self.pkg("%s -@ foo" % install_cmd, exit=2)
                self.pkg("%s -vq foo" % install_cmd, exit=2)
                self.pkg("%s" % install_cmd, exit=2)
                self.pkg("%s [email protected]" % install_cmd, exit=1)
                self.pkg("%s pkg:/[email protected]" % install_cmd, exit=1)

        def test_basics_1_install(self):
                """ Send empty package [email protected], install and uninstall """

                plist = self.pkgsend_bulk(self.rurl, self.foo10)
                self.image_create(self.rurl)

                self.pkg("list -a")
                self.pkg("list", exit=1)

                self.pkg("install --parsable=0 foo")
                self.assertEqualParsable(self.output,
                    add_packages=plist)

                self.pkg("list")
                self.pkg("verify")

                self.pkg("uninstall --parsable=0 foo")
                self.assertEqualParsable(self.output,
                    remove_packages=plist)
                self.pkg("verify")

        def test_basics_1_exact_install(self):
                """ Send empty package [email protected], exact-install and uninstall """

                plist = self.pkgsend_bulk(self.rurl, self.foo10)
                self.image_create(self.rurl)

                self.pkg("list -a")
                self.pkg("list", exit=1)

                self.pkg("exact-install --parsable=0 foo")
                self.assertEqualParsable(self.output,
                    add_packages=plist)

                # Exact-install the same version should do nothing.
                self.pkg("exact-install foo", exit=4)
                self.pkg("list")
                self.pkg("verify")

                self.pkg("uninstall --parsable=0 foo")
                self.assertEqualParsable(self.output,
                    remove_packages=plist)
                self.pkg("verify")

        def test_basics_2(self):
                """ Send package [email protected], containing a directory and a file,
                    install or exact-install, search, and uninstall. """

                self.basics_2_helper("install")
                self.basics_2_helper("exact-install")

        def basics_2_helper(self, install_cmd):
                # This test needs to use the depot to be able to test the
                # download cache.
                self.dc.start()

                self.pkgsend_bulk(self.durl, (self.foo10, self.foo11),
                    refresh_index=True)
                self.image_create(self.durl)

                self.pkg("list -a")
                self.pkg("%s foo" % install_cmd)

                # Verify that content cache is empty after successful install
                # or exact-install.
                api_inst = self.get_img_api_obj()
                img_inst = api_inst.img
                cache_dirs = []
                for path, readonly, pub, layout in img_inst.get_cachedirs():
                        if os.path.exists(path):
                                cache_dirs.extend(os.listdir(path))
                self.assertEqual(cache_dirs, [])

                self.pkg("verify")
                self.pkg("list")

                self.pkg("search -l /lib/libc.so.1")
                self.pkg("search -r /lib/libc.so.1")
                self.pkg("search -l blah", exit=1)
                self.pkg("search -r blah", exit=1)

                # check to make sure timestamp was set to correct value
                libc_path = os.path.join(self.get_img_path(), "lib/libc.so.1")
                lstat = os.stat(libc_path)

                assert (lstat[stat.ST_MTIME] == self.foo11_timestamp)

                # check that verify finds changes
                now = time.time()
                os.utime(libc_path, (now, now))
                self.pkg("verify", exit=1)

                self.pkg("uninstall foo")
                self.pkg("verify")
                self.pkg("list -a")
                self.pkg("verify")
                self.dc.stop()

        def test_basics_3(self):
                """Exact-install or Install [email protected], upgrade to [email protected],
                uninstall. """

                self.basics_3_helper("install")
                self.basics_3_helper("exact-install")

        def basics_3_helper(self, installed_cmd):
                # This test needs to use the depot to be able to test the
                # download cache.
                self.dc.start()

                self.pkgsend_bulk(self.durl, (self.foo10, self.foo11))
                self.image_create(self.durl)
                self.pkg("set-property flush-content-cache-on-success False")

                # Verify that content cache is empty before install or
                # exact-install.
                api_inst = self.get_img_api_obj()
                img_inst = api_inst.img
                cache_dirs = []
                for path, readonly, pub, layout in img_inst.get_cachedirs():
                        if os.path.exists(path):
                                cache_dirs.extend(os.listdir(path))
                self.assertEqual(cache_dirs, [])

                self.pkg("%s [email protected]" % installed_cmd)
                self.pkg("list [email protected]")
                self.pkg("list [email protected]", exit=1)

                self.pkg("%s [email protected]" % installed_cmd)

                # Verify that content cache is not empty after successful
                # install or exact_install (since
                # flush-content-cache-on-success is True by default) for
                # packages that have content.
                cache_dirs = []
                for path, readonly, pub, layout in img_inst.get_cachedirs():
                        if os.path.exists(path):
                                cache_dirs.extend(os.listdir(path))
                self.assertNotEqual(cache_dirs, [])

                self.pkg("list [email protected]")
                self.pkg("list [email protected]", exit=1)
                self.pkg("list foo@1")
                self.pkg("verify")

                self.pkg("uninstall foo")
                self.pkg("list -a")
                self.pkg("verify")
                self.dc.stop()

        def test_basics_4(self):
                """ Add [email protected], dependent on [email protected], exact-install or
                install, uninstall. """

                self.basics_4_helper("install")
                self.basics_4_helper("exact-install")

        def basics_4_helper(self, installed_cmd):
                # This test needs to use the depot to be able to test the
                # download cache.
                self.dc.start()

                self.pkgsend_bulk(self.durl, (self.foo10, self.foo11,
                    self.bar10))
                self.image_create(self.durl)

                # Set content cache to not be flushed on success.
                self.pkg("set-property flush-content-cache-on-success False")

                # Verify that content cache is empty before install or
                # exact-install.
                api_inst = self.get_img_api_obj()
                img_inst = api_inst.img
                cache_dirs = []
                for path, readonly, pub, layout in img_inst.get_cachedirs():
                        if os.path.exists(path):
                                cache_dirs.extend(os.listdir(path))
                self.assertEqual(cache_dirs, [])

                self.pkg("list -a")
                self.pkg("%s [email protected]" % installed_cmd)

                # Verify that content cache is not empty after successful
                # install or exact-install (since
                # flush-content-cache-on-success is False)
                # for packages that have content.
                cache_dirs = []
                for path, readonly, pub, layout in img_inst.get_cachedirs():
                        if os.path.exists(path):
                                cache_dirs.extend(os.listdir(path))
                self.assertNotEqual(cache_dirs, [])

                self.pkg("list")
                self.pkg("verify")
                self.pkg("uninstall -v bar foo")

                # foo and bar should not be installed at this point
                self.pkg("list bar", exit=1)
                self.pkg("list foo", exit=1)
                self.pkg("verify")
                self.dc.stop()

        def test_basics_5_install(self):
                """ Install [email protected], upgrade to [email protected].
                Boring should be left alone, while
                foo gets upgraded as needed"""

                self.pkgsend_bulk(self.rurl, (self.bar10, self.bar11,
                    self.foo10, self.foo11, self.foo12, self.boring10,
                    self.boring11))
                self.image_create(self.rurl)

                self.pkg("install [email protected] [email protected] [email protected]")
                self.pkg("list")
                self.pkg("list [email protected] [email protected] [email protected]")
                self.pkg("install -v [email protected]") # upgrade bar
                self.pkg("list")
                self.pkg("list [email protected] [email protected] [email protected]")

        def test_basics_5_exact_install(self):
                """ exact-nstall [email protected], upgrade to [email protected].
                Boring should be left alone, while
                foo gets upgraded as needed"""

                self.pkgsend_bulk(self.rurl, (self.bar10, self.bar11,
                    self.foo10, self.foo11, self.foo12, self.boring10,
                    self.boring11))
                self.image_create(self.rurl)

                self.pkg("exact-install [email protected] [email protected] [email protected]")
                self.pkg("list [email protected] [email protected] [email protected]")
                self.pkg("exact-install -v [email protected]") # upgrade bar
                self.pkg("list [email protected] [email protected]")
                self.pkg("list [email protected]", exit=1)

        def test_basics_6_install(self):
                """Verify that '@latest' will install the latest version
                of a package."""

                # Create a repository for the test publisher.
                t1dir = os.path.join(self.test_root, "test-repo")
                self.create_repo(t1dir, properties={ "publisher": {
                    "prefix": "test" } })
                self.pkgsend_bulk(t1dir, (self.bar10,
                    self.foo10, self.foo11, self.foo12, self.boring10,
                    self.boring11))
                self.image_create("file:%s" % t1dir)

                # Create a repository for a different publisher for at
                # least one of the packages so that we can verify that
                # publisher search order is accounted for by @latest.
                # The second publisher is called 'pub2' here so that
                # it comes lexically before 'test' (see bug 18180) to
                # ensure that latest version ordering works correctly
                # when the same stem is provided by different publishers.
                t2dir = os.path.join(self.test_root, "pub2-repo")
                self.create_repo(t2dir, properties={ "publisher": {
                    "prefix": "pub2" } })
                self.pkgsend_bulk(t2dir, self.bar11)

                self.pkg("set-publisher -p %s" % t2dir)
                self.pkg("install '*@latest'")

                # 1.0 of bar should be installed here since pub2 is a
                # lower-ranked publisher.
                self.pkg("list")
                self.pkg("info [email protected] [email protected] [email protected]")

                self.pkg("set-publisher --non-sticky test")
                self.pkg("set-publisher -P pub2 ")
                self.pkg("install bar@latest")

                # 1.1 of bar should be installed here since pub2 is a
                # higher-ranked publisher and test is non-sticky.
                self.pkg("list")
                self.pkg("info [email protected]")

                # Cleanup.
                shutil.rmtree(t1dir)
                shutil.rmtree(t2dir)

        def test_basics_6_exact_install(self):
                """Verify that '@latest' will install the latest version
                of a package."""

                # Create a repository for the test publisher.
                t1dir = os.path.join(self.test_root, "test-repo")
                self.create_repo(t1dir, properties={ "publisher": {
                    "prefix": "test" } })
                self.pkgsend_bulk(t1dir, (self.bar10,
                    self.foo10, self.foo11, self.foo12, self.boring10,
                    self.boring11))
                self.image_create("file:%s" % t1dir)

                # Create a repository for a different publisher for at
                # least one of the packages so that we can verify that
                # publisher search order is accounted for by @latest.
                # The second publisher is called 'pub2' here so that
                # it comes lexically before 'test' (see bug 18180) to
                # ensure that latest version ordering works correctly
                # when the same stem is provided by different publishers.
                t2dir = os.path.join(self.test_root, "pub2-repo")
                self.create_repo(t2dir, properties={ "publisher": {
                    "prefix": "pub2" } })
                self.pkgsend_bulk(t2dir, self.bar11)

                self.pkg("set-publisher -p %s" % t2dir)
                self.pkg("exact-install '*@latest'")

                # 1.0 of bar should be installed here since pub2 is a
                # lower-ranked publisher.
                self.pkg("list")
                self.pkg("info [email protected] [email protected] [email protected]")

                self.pkg("set-publisher --non-sticky test")
                self.pkg("set-publisher -P pub2 ")
                self.pkg("exact-install bar@latest")

                # 1.1 of bar should be installed here since pub2 is a
                # higher-ranked publisher and test is non-sticky.
                self.pkg("info [email protected]")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]", exit=1)

                # Cleanup.
                shutil.rmtree(t1dir)
                shutil.rmtree(t2dir)

        def test_basics_7_install(self):
                """ Add [email protected], install [email protected]. """

                self.pkgsend_bulk(self.rurl, self.xbar11)
                self.image_create(self.rurl)
                self.pkg("install [email protected]", exit=1)

        def test_basics_7_exact_install(self):
                """Add [email protected], exact-install [email protected]."""

                self.pkgsend_bulk(self.rurl, self.xbar11)
                self.image_create(self.rurl)
                self.pkg("exact-install [email protected]", exit=1)

        def test_basics_8_exact_install(self):
                """Try to exact-install two versions of the same package."""

                self.pkgsend_bulk(self.rurl, (self.foo11, self.foo12))
                self.image_create(self.rurl)

                self.pkg("exact-install [email protected] [email protected]", exit=1)

        def test_basics_9_exact_install(self):
                """Verify downgrade will fail with exact-install."""

                self.pkgsend_bulk(self.rurl, (self.bar10, self.bar11,
                    self.foo10, self.foo12))
                self.image_create(self.rurl)

                self.pkg("install [email protected]")
                self.pkg("exact-install [email protected]", exit=1)
                self.pkg("list [email protected]")
                self.pkg("list [email protected]")

        def test_freeze_exact_install(self):
                """Verify frozen packages can be relaxed with exact_install.
                Which means we can ignore the frozen list with exact_install.
                """

                self.pkgsend_bulk(self.rurl, (self.fbar10, self.ffoo10,
                    self.fbar11, self.ffoo11))
                self.image_create(self.rurl)

                # Freeze [email protected].
                self.pkg("install [email protected]")
                self.pkg("freeze [email protected]")
                self.pkg("exact-install [email protected]")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]")
                self.pkg("uninstall '*'")

                # Freeze both [email protected] and [email protected].
                self.image_create(self.rurl)
                self.pkg("install [email protected] [email protected]")
                self.pkg("freeze [email protected]")
                self.pkg("freeze [email protected]")
                self.pkg("exact-install [email protected]")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]")

        def test_basics_mdns(self):
                """ Send empty package [email protected], install and uninstall """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.pkg("set-property mirror-discovery True")
                self.pkg("list -a")
                self.pkg("list", exit=1)

                self.pkg("install foo")

                self.pkg("list")
                self.pkg("verify")

                self.pkg("uninstall foo")
                self.pkg("verify")

        def test_basics_ipv6(self):
                """Verify package operations can be performed using a depot
                server being addressed via IPv6."""

                # This test needs to use the depot to be able to test
                # IPv6 connectivity.
                self.dc.set_address("::1")
                self.dc.start()

                durl = self.dc.get_depot_url()
                self.pkgsend_bulk(durl, (self.foo10, self.foo12))
                self.wait_repo(self.dc.get_repodir())
                self.image_create(durl)

                self.pkg("install [email protected]")
                self.pkg("info [email protected]")

                self.pkg("update")
                self.pkg("list")
                self.pkg("info [email protected]")
                self.pkg("uninstall '*'")
                self.dc.stop()
                self.dc.set_address(None)

        def test_image_upgrade(self):
                """ Send package [email protected], dependent on [email protected].  Install
                or exact-install [email protected].  List all packages.  Upgrade image.
                """

                self.image_upgrade_helper("install")
                self.image_upgrade_helper("exact-install")

        def image_upgrade_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, (self.foo10, self.foo11,
                    self.bar10))
                self.image_create(self.rurl)

                self.pkg("%s [email protected]" % install_cmd)

                self.pkgsend_bulk(self.rurl, (self.foo12, self.bar11))
                self.pkg("refresh")

                self.pkg("contents -H")
                self.pkg("list")
                self.pkg("refresh")

                self.pkg("list")
                self.pkg("verify")
                self.pkg("update -v")
                self.pkg("verify")

                self.pkg("list [email protected]")
                self.pkg("list [email protected]")

                self.pkg("uninstall bar foo")
                self.pkg("verify")

        def test_dependent_uninstall(self):
                """Trying to remove a package that's a dependency of another
                package should fail since uninstall isn't recursive."""

                self.pkgsend_bulk(self.rurl, (self.foo10, self.bar10))
                self.image_create(self.rurl)

                self.pkg("install [email protected]")

                self.pkg("uninstall -v foo", exit=1)
                self.pkg("list bar")
                self.pkg("list foo")

        def test_bug_1338(self):
                """ Add [email protected], dependent on [email protected], install [email protected]. """

                self.pkg("list -a")
                self.pkgsend_bulk(self.rurl, self.bar11)
                self.image_create(self.rurl)

                self.pkg("install [email protected]", exit=1)

        def test_bug_1338_2(self):
                """ Add [email protected], dependent on [email protected], and [email protected], dependent
                    on [email protected], install [email protected] and [email protected]. """

                self.pkgsend_bulk(self.rurl, (self.bar11, self.baz10))
                self.image_create(self.rurl)
                self.pkg("list -a")
                self.pkg("install [email protected] [email protected]")

        def test_bug_1338_3(self):
                """ Add [email protected], [email protected]. [email protected] depends on [email protected] which
                    depends on [email protected], install [email protected]. """

                self.pkgsend_bulk(self.rurl, (self.xbar10, self.xdeep10))
                self.image_create(self.rurl)

                self.pkg("install [email protected]", exit=1)

        def test_bug_1338_4(self):
                """ Add [email protected]. [email protected] depends on [email protected] which depends
                on [email protected], install [email protected]. """

                self.pkgsend_bulk(self.rurl, self.ydeep10)
                self.image_create(self.rurl)

                self.pkg("install [email protected]", exit=1)

        def test_bug_2795(self):
                """ Try to install two versions of the same package """

                self.pkgsend_bulk(self.rurl, (self.foo11, self.foo12))
                self.image_create(self.rurl)

                self.pkg("install [email protected] [email protected]", exit=1)

        def test_bug_6018(self):
                """  From original comment in bug report:

                Consider a repository that contains:

                a@1 and a@2

                b@1 that contains an optional dependency on package a@1

                If a@1 and b@1 are installed in an image, the "pkg update" command
                produces the following output:

                $ pkg update
                No updates available for this image.

                However, "pkg install a@2" works.
                """

                plist = self.pkgsend_bulk(self.rurl, (self.a6018_1,
                    self.a6018_2, self.b6018_1))
                self.image_create(self.rurl)
                self.pkg("install b6018@1 a6018@1")
                # Test the parsable output of update.
                self.pkg("update --parsable=0")
                self.assertEqualParsable(self.output,
                    change_packages=[[plist[0], plist[1]]])
                self.pkg("list b6018@1 a6018@2")

        def test_install_matching(self):
                """ Try to [un]install or exact-install packages matching a
                pattern """

                self.install_matching_helper("install")
                self.install_matching_helper("exact-install")

        def install_matching_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, (self.foo10, self.bar10,
                    self.baz10))
                self.image_create(self.rurl)
                # don't specify versions here; we have many
                # different versions of foo, bar & baz in repo
                # when entire class is run w/ one repo instance.

                # first case should fail since multiple patterns
                # match the same pacakge
                self.pkg("%s 'ba*' 'b*'" % install_cmd, exit=1)
                self.pkg("%s 'ba*'" % install_cmd, exit=0)
                self.pkg("list foo", exit=0)
                self.pkg("list bar", exit=0)
                self.pkg("list baz", exit=0)
                self.pkg("uninstall 'b*' 'f*'")

                # However, multiple forms of the same pattern should simply be
                # coalesced and allowed.
                self.pkg("%s pkg:/foo /foo ///foo pkg:///foo" % install_cmd)
                self.pkg("list")
                self.pkg("verify pkg:/foo /foo ///foo pkg:///foo")
                self.pkg("uninstall pkg:/foo /foo ///foo "
                    "pkg:///foo")

        def test_bad_package_actions(self):
                """Test the install of packages that have actions that are
                invalid."""

                self.bad_package_actions("install")
                self.bad_package_actions("exact-install")

        def bad_package_actions(self, install_cmd):
                # First, publish the package that will be corrupted and create
                # an image for testing.
                plist = self.pkgsend_bulk(self.rurl, (self.badfile10,
                    self.baddir10))
                self.image_create(self.rurl)

                # This should succeed and cause the manifest to be cached.
                self.pkg("%s %s" % (install_cmd, " ".join(plist)))

                # While the manifest is cached, get a copy of its contents.
                for p in plist:
                        pfmri = fmri.PkgFmri(p)
                        mdata = self.get_img_manifest(pfmri)
                        if mdata.find("dir") != -1:
                                src_mode = "mode=755"
                        else:
                                src_mode = "mode=644"

                        # Now remove the package so corrupt case can be tested.
                        self.pkg("uninstall %s" % pfmri.pkg_name)

                        # Now attempt to corrupt the client's copy of the
                        # manifest in various ways to check if the client
                        # handles missing mode and invalid mode cases for
                        # file and directory actions.
                        for bad_mode in ("", 'mode=""', "mode=???"):
                                self.debug("Testing with bad mode "
                                    "'%s'." % bad_mode)
                                bad_mdata = mdata.replace(src_mode, bad_mode)
                                self.write_img_manifest(pfmri, bad_mdata)
                                self.pkg("--debug manifest_validate=Never "
                                    "%s %s" % (install_cmd, pfmri.pkg_name),
                                    exit=1)

                        # Now attempt to corrupt the client's copy of the
                        # manifest in various ways to check if the client
                        # handles missing or invalid owners and groups.
                        for bad_owner in ("", 'owner=""', "owner=invaliduser"):
                                self.debug("Testing with bad owner "
                                    "'%s'." % bad_owner)

                                bad_mdata = mdata.replace("owner=root",
                                    bad_owner)
                                self.write_img_manifest(pfmri, bad_mdata)
                                self.pkg("--debug manifest_validate=Never "
                                    "%s %s" % (install_cmd, pfmri.pkg_name),
                                    exit=1)

                        for bad_group in ("", 'group=""', "group=invalidgroup"):
                                self.debug("Testing with bad group "
                                    "'%s'." % bad_group)

                                bad_mdata = mdata.replace("group=bin",
                                    bad_group)
                                self.write_img_manifest(pfmri, bad_mdata)
                                self.pkg("--debug manifest_validate=Never "
                                    "%s %s" % (install_cmd, pfmri.pkg_name),
                                    exit=1)

                        # Now attempt to corrupt the client's copy of the
                        # manifest such that actions are malformed.
                        for bad_act in (
                            'set name=description value="" \" my desc \" ""',
                            "set name=com.sun.service.escalations value="):
                                self.debug("Testing with bad action "
                                    "'%s'." % bad_act)
                                bad_mdata = mdata + "%s\n" % bad_act
                                self.write_img_manifest(pfmri, bad_mdata)
                                self.pkg("--debug manifest_validate=Never "
                                    "%s %s" % (install_cmd, pfmri.pkg_name),
                                    exit=1)

        def test_bug_3770(self):
                """ Try to install a package from a publisher with an
                unavailable repository. """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                # Depot hasn't been started, so client can't connect.
                self.pkg("set-publisher --no-refresh -O %s test" % self.durl)
                self.pkg("install [email protected]", exit=1)

        def test_bug_9929(self):
                """Make sure that we can uninstall a package that already
                has its contents on disk even when the repository isn't
                accessible."""

                # Depot required for this test since client doesn't cache
                # files from a file repository by default.
                self.dc.start()
                self.pkgsend_bulk(self.durl, self.foo11)
                self.image_create(self.durl)

                self.pkg("install foo")

                # Stop depot, so client can't connect.
                self.dc.stop()
                self.pkg("set-publisher --no-refresh -O %s test" % self.durl)
                self.pkg("uninstall foo")

        def test_bug_16189(self):
                """Create a repository with a pair of manifests.  Have
                pkg A depend upon pkg B.  Rename pkg B on the depot-side
                and attempt to install. This should fail, but not traceback.
                Then, modify the manifest on the serverside, ensuring that
                the contents don't match the hash.  Then try the install again.
                This should also fail, but not traceback."""

                afmri = self.pkgsend_bulk(self.rurl, self.a16189)
                bfmri = self.pkgsend_bulk(self.rurl, self.b16189)
                self.image_create(self.rurl)

                repo = self.dc.get_repo()

                bpath = repo.manifest(bfmri[0])
                old_dirname = os.path.basename(os.path.dirname(bpath))
                old_dirpath = os.path.dirname(os.path.dirname(bpath))
                new_dirname = "potato"
                old_path = os.path.join(old_dirpath, old_dirname)
                new_path = os.path.join(old_dirpath, new_dirname)
                os.rename(old_path, new_path)
                self.pkg("install a16189", exit=1)

                os.rename(new_path, old_path)
                self.image_destroy()
                self.image_create(self.rurl)

                apath = repo.manifest(afmri[0])
                afobj = open(apath, "w")
                afobj.write("set name=pkg.summary value=\"banana\"\n")
                afobj.close()
                self.pkg("install a16189", exit=1)

        def test_install_fuzz(self):
                """Verify that packages delivering files with whitespace in
                their paths can be installed or exact-installed, updated, and
                uninstalled."""

                self.pkgsend_bulk(self.rurl, self.fuzzy)
                self.image_create(self.rurl)

                self.pkg("install fuzzy@1")
                self.pkg("verify -v")
                self.pkg("update -vvv fuzzy@2")
                self.pkg("verify -v")

                for name in (
                    "opt/dir with white\tspace/cat in a hat",
                    "etc/cat_link",
                ):
                        self.debug("fname: %s" % name)
                        self.assert_(os.path.exists(os.path.join(self.get_img_path(),
                            name)))

                self.pkg("uninstall -vvv fuzzy")

        def test_sysattrs(self):
                """Test install with setting system attributes."""

                if portable.osname != "sunos":
                        raise pkg5unittest.TestSkippedException(
                            "System attributes unsupported on this platform.")

                plist = self.pkgsend_bulk(self.rurl, [self.secret1,
                    self.secret2, self.secret3, self.secret4])

                # Need to create an image in /var/tmp since sysattrs don't work
                # in tmpfs.
                self.debug(self.rurl)
                old_img_path = self.img_path()
                self.set_img_path(tempfile.mkdtemp(dir="/var/tmp"))

                self.image_create(self.rurl)

                # test without permission for setting sensitive system attribute
                self.pkg("install secret1", su_wrap=True, exit=1)

                # now some tests which should succeed
                self.pkg("install secret1")
                fpath = os.path.join(self.img_path(),"p1/cat")
                p = subprocess.Popen(["/usr/bin/ls", "-/", "c", fpath],
                    stdout=subprocess.PIPE)
                out, err = p.communicate()
                expected = "{AH-----m----T}"
                self.assertTrue(expected in out, out)

                self.pkg("install secret2")
                fpath = os.path.join(self.img_path(),"p2/cat")
                p = subprocess.Popen(["/usr/bin/ls", "-/", "c", fpath],
                    stdout=subprocess.PIPE)
                out, err = p.communicate()
                expected = "{AH-----m----T}"
                self.assertTrue(expected in out, out)

                # test some packages with invalid sysattrs
                self.pkg("install secret3", exit=1)
                self.pkg("install secret4", exit=1)
                shutil.rmtree(self.img_path())
                self.set_img_path(old_img_path)


class TestPkgInstallApache(pkg5unittest.ApacheDepotTestCase):

        # Only start/stop the depot once (instead of for every test)
        persistent_setup = True

        foo11 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=/lib
            add file tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.so.1 timestamp="20080731T024051Z"
            close """

        upgrade_np10 = """
            open [email protected],5.11-0
            close"""

        misc_files = [ "tmp/libc.so.1" ]

        def setUp(self):
                pkg5unittest.ApacheDepotTestCase.setUp(self, ["test1", "test2"],
                    start_depots=True)
                self.make_misc_files(self.misc_files)
                self.durl1 = self.dcs[1].get_depot_url()
                self.durl2 = self.dcs[2].get_depot_url()
                self.pkgsend_bulk(self.durl1, self.foo11)
                self.pkgsend_bulk(self.durl2, self.upgrade_np10)

        def test_corrupt_web_cache(self):
                """Make sure the client can detect corrupt content being served
                to it from a corrupt web cache, modifying its requests to
                retrieve correct content."""

                fmris = self.pkgsend_bulk(self.durl1, (self.foo11,
                    self.upgrade_np10))
                # we need to record just the version string of foo in order
                # to properly quote it later.
                foo_version = fmris[0].split("@")[1]
                self.image_create(self.durl1)

                # we use the system repository as a convenient way to setup
                # a caching proxy
                self.sysrepo("")
                sc_runtime_dir = os.path.join(self.test_root, "sysrepo_runtime")
                sc_conf = os.path.join(sc_runtime_dir, "sysrepo_httpd.conf")
                sc_cache = os.path.join(self.test_root, "sysrepo_cache")

                # ensure pkg5srv can write cache content
                os.chmod(sc_cache, 0777)

                sysrepo_port = self.next_free_port
                self.next_free_port += 1
                sc = pkg5unittest.SysrepoController(sc_conf,
                    sysrepo_port, sc_runtime_dir, testcase=self)
                self.register_apache_controller("sysrepo", sc)
                sc.start()

                sysrepo_url = "http://localhost:%s" % sysrepo_port

                saved_pkg_sysrepo_env = os.environ.get("PKG_SYSREPO_URL")
                os.environ["PKG_SYSREPO_URL"] = sysrepo_url

                # create an image, installing a package, to warm up the webcache
                self.image_create(props={"use-system-repo": True})
                self.pkg("install [email protected]")
                self.pkg("uninstall foo")

                # now recreate the image.  image_create calls image_destroy,
                # thereby cleaning any cached content in the image.
                self.image_create(props={"use-system-repo": True})

                def corrupt_path(path, value="noodles\n", rename=False):
                        """Given a path, corrupt its contents."""
                        self.assert_(os.path.exists(path))
                        if rename:
                                os.rename(path, path + ".not-corrupt")
                                open(path, "wb").write(value)
                        else:
                                df = open(path, "wb")
                                df.write(value)
                                df.close()

                def corrupt_cache(cache_dir):
                        """Given an apache cache, corrupt it's contents."""

                        for dirpath, dirname, filenames in os.walk(cache_dir):
                                for name in filenames:
                                        if name.endswith(".header"):
                                                data = name.replace(".header",
                                                    ".data")
                                                corrupt_path(os.path.join(
                                                    dirpath, data))
                # corrupt our web cache
                corrupt_cache(sc_cache)

                urls = [
                    # we need to quote the version carefully to use exactly the
                    # format pkg(1) uses - two logically identical urls that
                    # differ only by the way they're quoted are treated by
                    # Apache as separate cacheable resources.
                    "%s/test1/manifest/0/foo@%s" % (self.durl1, urllib2.quote(
                    foo_version)),
                    "%s/test1/file/1/8535c15c49cbe1e7cb1a0bf8ff87e512abed66f8" %
                    self.durl1,
                ]

                proxy_handler = urllib2.ProxyHandler({"http": sysrepo_url})
                proxy_opener = urllib2.build_opener(proxy_handler)

                # validate that our cache is returning corrupt urls.
                for url in urls:
                        self.debug("url:%s" % url)
                        # we should get clean content when we don't use the
                        # cache
                        u = urllib2.urlopen(url)
                        content = u.readlines()
                        self.assert_(content != ["noodles\n"],
                            "Unexpected content from depot")

                        # get the corrupted version, and verify it is broken
                        req = urllib2.Request(url)
                        u = proxy_opener.open(req)
                        content = u.readlines()

                        self.assert_(content == ["noodles\n"],
                            "Expected noodles, got %s for %s" % (content, url))

                # the following should work, as pkg should retry requests
                # where it has detected corrupt contents with a
                # "Cache-Control: no-cache" header.
                self.pkg("refresh --full")
                self.pkg("contents -rm [email protected]")
                self.pkg("install [email protected]")

                # since the cache has been refreshed, we should see valid
                # contents when going through the proxy now.
                for url in urls:
                        req = urllib2.Request(url)
                        u = proxy_opener.open(req)
                        content = u.readlines()
                        self.assert_(content != ["noodles\n"],
                            "Unexpected content from depot")

                # ensure that when we actually corrupt the repository
                # as well as the cache, we do detect the errors properly.
                corrupt_cache(sc_cache)
                repodir = self.dcs[1].get_repodir()

                prefix = "publisher/test1"
                self.image_create(props={"use-system-repo": True})

                # When we corrupt the files in the repository, we intentionally
                # corrupt them with different contents than the the cache,
                # allowing us to check the error messages being printed by the
                # transport subsystem.

                filepath = os.path.join(repodir,
                    "%s/file/85/8535c15c49cbe1e7cb1a0bf8ff87e512abed66f8" %
                    prefix)
                mfpath = os.path.join(repodir, "%s/pkg/foo/%s" % (prefix,
                    urllib2.quote(foo_version)))
                catpath = os.path.join(repodir, "%s/catalog/catalog.base.C" %
                    prefix)

                try:
                        # first corrupt the file
                        corrupt_path(filepath, value="spaghetti\n", rename=True)
                        self.pkg("install [email protected]", stderr=True, exit=1)
                        os.rename(filepath + ".not-corrupt", filepath)

                        # we should be getting two hash errors, one from the
                        # cache, one from the repo. The one from the repo should
                        # repeat
                        self.assert_(
                            "1: Invalid contentpath lib/libc.so.1: chash" in
                            self.errout)
                        self.assert_(
                            "2: Invalid contentpath lib/libc.so.1: chash" in
                            self.errout)
                        self.assert_("(happened 3 times)" in self.errout)

                        # now corrupt the manifest (we have to re-corrupt the
                        # cache, since attempting to install foo above would
                        # have caused the cache to refetch the valid manifest
                        # from the repo) and remove the version of the manifest
                        # cached in the image.
                        corrupt_cache(sc_cache)
                        corrupt_path(mfpath, value="spaghetti\n", rename=True)
                        shutil.rmtree(os.path.join(self.img_path(),
                            "var/pkg/publisher/test1/pkg"))
                        self.pkg("contents -rm [email protected]", stderr=True, exit=1)
                        os.rename(mfpath + ".not-corrupt", mfpath)

                        # we should get two hash errors, one from the cache, one
                        # from the repo - the one from the repo should repeat.
                        self.assert_(
                            "1: Invalid content: manifest hash failure" in
                            self.errout)
                        self.assert_("2: Invalid content: manifest hash failure"
                            in self.errout)
                        self.assert_("(happened 3 times)" in self.errout)

                        # finally, corrupt the catalog. Given we've asked for a
                        # full refresh, we retrieve the upstream version only.
                        corrupt_path(catpath, value="spaghetti\n", rename=True)
                        self.pkg("refresh --full", stderr=True, exit=1)
                        self.assert_("catalog.base.C' is invalid." in
                            self.errout)
                        os.rename(catpath + ".not-corrupt", catpath)

                finally:
                        # make sure we clean up any corrupt repo contents.
                        for path in [filepath, mfpath, catpath]:
                                not_corrupt = path + ".not-corrupt"
                                if os.path.exists(not_corrupt):
                                        os.rename(not_corrupt, path)

                        if saved_pkg_sysrepo_env:
                                os.environ["PKG_SYSREPO_URL"] = \
                                    saved_pkg_sysrepo_env

        def test_granular_proxy(self):
                """Tests that images can use the set-publisher --proxy argument
                to selectively proxy requests."""

                # we use the system repository as a convenient way to setup
                # a caching proxy.   Since the image doesn't have the property
                # 'use-system-repo=True', the configuration of the sysrepo
                # will remain static.
                self.image_create(self.durl1)
                self.sysrepo("")
                sc_runtime_dir = os.path.join(self.test_root, "sysrepo_runtime")
                sc_conf = os.path.join(sc_runtime_dir, "sysrepo_httpd.conf")
                sc_cache = os.path.join(self.test_root, "sysrepo_cache")

                # ensure pkg5srv can write cache content
                os.chmod(sc_cache, 0777)

                sysrepo_port = self.next_free_port
                self.next_free_port += 1
                sc = pkg5unittest.SysrepoController(sc_conf,
                    sysrepo_port, sc_runtime_dir, testcase=self)
                self.register_apache_controller("sysrepo", sc)
                sc.start()
                sysrepo_url = "http://localhost:%s" % sysrepo_port

                self.image_create()
                self.pkg("set-publisher -p %s --proxy %s" % (self.durl1,
                    sysrepo_url))
                self.pkg("install foo")
                self.pkg("uninstall foo")

                sc.stop()
                # with our proxy offline, and with no other origins
                # available, we should be unable to install
                self.pkg("install --no-refresh foo", exit=1)
                sc.start()

                # we cannot add another origin with the same url
                self.pkg("set-publisher --no-refresh -g %s test1" %
                    self.durl1, exit=1)
                # we cannot add another proxied origin with that url
                self.pkg("set-publisher --no-refresh -g %s "
                    "--proxy http://noodles test1" % self.durl1,
                    exit=1)

                # Now add a second, unproxied publisher, ensuring we
                # can install packages from there.  Since the proxy
                # isn't configured to proxy that resource, this tests
                # that the proxy for self.durl1 isn't being used.
                self.pkg("set-publisher -g %s test2" % self.durl2)
                self.pkg("install --no-refresh "
                    "pkg://test2/[email protected]")
                self.pkg("uninstall pkg://test2/[email protected]")
                self.pkg("set-publisher -G %s test2" % self.durl2)

                # check that runtime proxies are being used - we
                # set a bogus proxy, then ensure our $http_proxy value
                # gets used.
                self.pkg("publisher")
                self.pkg("set-publisher -G %s test1" % self.durl1)
                self.pkg("set-publisher --no-refresh -g %s "
                    "--proxy http://noodles test1" % self.durl1)
                env = {"http_proxy": sysrepo_url}
                self.pkg("refresh", env_arg=env)
                self.pkg("install foo", env_arg=env)
                self.pkg("uninstall foo", env_arg=env)

                # check that $all_proxy works
                env = {"all_proxy": sysrepo_url}
                self.pkg("install foo", env_arg=env)
                self.pkg("uninstall foo", env_arg=env)

                # now check that no_proxy works
                env["no_proxy"] = "*"
                self.pkg("install foo", env_arg=env)
                self.pkg("refresh --full", env_arg=env)


class TestPkgInstallRepoPerTest(pkg5unittest.SingleDepotTestCase):
        persistent_setup = False
        # Tests in this suite use the read only data directory.
        need_ro_data = True

        foo10 = """
            open [email protected],5.11-0
            close """

        disappear10 = """
            open [email protected],5.11-0
            add file tmp/cat mode=0555 owner=root group=bin path=/bin/cat
            close """

        disappear11 = """
            open [email protected],5.11-0
            close """

        misc_files = ["tmp/cat"]

        def setUp(self):
                pkg5unittest.SingleDepotTestCase.setUp(self)
                self.make_misc_files(self.misc_files)

        def test_install_changed_manifest(self):
                """Test that if a manifest that is being installed is cached
                locally has been changed on the repo is updated, the new
                manifest is used."""

                plist = self.pkgsend_bulk(self.rurl, self.foo10)

                self.image_create(self.rurl)
                self.seed_ta_dir("ta3")
                api_inst = self.get_img_api_obj()

                # Use pkg contents to cache the manifest.
                self.pkg("contents -r foo")

                # Specify location as filesystem path.
                self.pkgsign_simple(self.dc.get_repodir(), plist[0])

                # Ensure that the image requires signed manifests.
                self.pkg("set-property signature-policy require-signatures")
                api_inst.reset()

                # Install the package
                self._api_install(api_inst, ["foo"])

        def test_keep_installed_changed_manifest(self):
                """Test that if a manifest that has been installed is changed on
                the server is updated, the installed manifest is not changed."""

                pfmri = self.pkgsend_bulk(self.rurl, self.disappear10)[0]

                self.image_create(self.rurl)
                api_inst = self.get_img_api_obj()

                # Install the package
                self._api_install(api_inst, ["disappear"])

                self.assert_(os.path.isfile(os.path.join(
                    self.img_path(), "bin", "cat")))
                repo = self.dc.get_repo()
                m_path = repo.manifest(pfmri)
                with open(m_path, "rb") as fh:
                        fmri_lines = fh.readlines()
                with open(m_path, "wb") as fh:
                        for l in fmri_lines:
                                if "usr/bin/cat" in l:
                                        continue
                                fh.write(l)
                repo.rebuild()

                pfmri = self.pkgsend_bulk(self.rurl, self.disappear11)[0]
                self._api_update(api_inst)
                self.assert_(not os.path.isfile(os.path.join(
                    self.img_path(), "bin", "cat")))


class TestPkgActuators(pkg5unittest.SingleDepotTestCase):
        """Test package actuators"""
        persistent_setup = True

        pkgs = (
                """
                    open [email protected],5.11-0
                    close """,
                """
                    open [email protected],5.11-0
                    close """,
                """
                    open [email protected],5.11-0
                    close """,
                """
                    open [email protected],5.11-0
                    close """,
                """
                    open [email protected],5.11-0
                    close """,
                """
                    open [email protected],5.11-0
                    add depend type=require fmri=trigger
                    close """,
                """
                    open [email protected],5.11-0
                    add set name=pkg.additional-update-on-uninstall value=A@2
                    close """,
                """
                    open [email protected],5.11-0
                    add set pkg.additional-update-on-uninstall=A@1
                    close """,
                """
                    open [email protected],5.11-0
                    add set name=pkg.additional-uninstall-on-uninstall value=A
                    close """,
                """
                    open [email protected],5.11-0
                    add set pkg.additional-uninstall-on-uninstall=A
                    close """,
                """
                    open [email protected],5.11-0
                    add set name=pkg.additional-uninstall-on-uninstall value=A@2 value=B@2
                    close """,
                """
                    open [email protected],5.11-0
                    add set name=pkg.additional-uninstall-on-uninstall value=A@1
                    close """,
                """
                    open [email protected],5.11-0
                    add set name=pkg.additional-uninstall-on-uninstall value=C
                    close """,
                """
                    open [email protected],5.11-0
                    add set name=pkg.additional-update-on-uninstall value=evil@2
                    close """,
                """
                    open [email protected],5.11-0
                    close """,
                )

        def setUp(self):
                pkg5unittest.SingleDepotTestCase.setUp(self)
                self.pkgsend_bulk(self.rurl, self.pkgs)

        def test_basics(self):
                """Test that pkg actuators work as expected."""
                # prepare image
                self.image_create(self.rurl)
                self.pkg("install A@1")
                self.pkg("install -v trigger@1")
                self.pkg("list A@1 trigger@1")

                # update on uninstall
                self.pkg("uninstall -v trigger")
                self.pkg("list A@2")

                self.pkg("install -v trigger@2")
                self.pkg("uninstall -v trigger")
                self.pkg("list A@1")

                # uninstall on uninstall
                self.pkg("install -v trigger@3")
                self.pkg("uninstall -v trigger")
                self.pkg("list A", exit=1)

                # verify unversioned actuator triggers
                self.pkg("install -v trigger@4 A@1")
                self.pkg("uninstall -v trigger")
                self.pkg("list A", exit=1)
                self.pkg("install -v trigger@4 A@2")
                self.pkg("uninstall -v trigger")
                self.pkg("list A", exit=1)

                # verify correct version is uninstalled
                self.pkg("install -v trigger@6 A@1")
                self.pkg("uninstall -v trigger")
                self.pkg("list A", exit=1)

                # verify non-matching version is not installed
                self.pkg("install -v trigger@6 A@2")
                self.pkg("uninstall -v trigger")
                self.pkg("list A")

                # multiple values
                self.pkg("install -v trigger@5 A@2 B@2")
                self.pkg("uninstall -v trigger")
                self.pkg("list A B", exit=1)

                # multiple values but at different versions
                self.pkg("install -v trigger@5 A@1 B@1")
                self.pkg("uninstall -v trigger")
                self.pkg("list A@1 B@1")

                # removal pkg depends on trigger
                self.pkg("install -v trigger@7 C")
                self.pkg("uninstall -v trigger")
                self.pkg("list C", exit=1)

                # test that uninstall actuators also work when pkg is rejected
                self.pkg("install -v A@1 trigger@1")
                self.pkg("list A@1")
                # install with reject
                self.pkg("install --reject trigger B@1")
                self.pkg("list A@2")
                # update with reject
                self.pkg("install -v trigger@2")
                self.pkg("update -v --reject trigger B@2")
                self.pkg("list A@1")

                # self-referencing (evil) pkgs
                self.pkg("install -v evil@1")
                # solver will complain about passing same pkg to reject and
                # proposed dict
                self.pkg("uninstall -v evil@1", exit=1)
                # try workaround
                self.cmdline_run("pkg -R %s -D ignore-pkg-actuators=true " \
                    "uninstall -v evil@1" % self.get_img_path())
                self.pkg("list evil", exit=1)

                # Test overlapping user and actuator pkg requests.
                # Since actuators are treated like user requests, the solver
                # will pick the latest one.
                self.pkg("install -v A@1 trigger@1")
                # update with reject
                self.pkg("update --parsable=0 --reject trigger [email protected]")
                self.pkg("list A@2")


class TestPkgInstallUpdateReject(pkg5unittest.SingleDepotTestCase):
        """Test --reject option to pkg update/install"""
        persistent_setup = True

        pkgs = (
                """
                    open [email protected],5.11-0
                    add depend type=require-any fmri=pkg:/[email protected] fmri=pkg:/[email protected]
                    close """,
                """
                    open [email protected],5.11-0
                    add depend type=require-any fmri=pkg:/[email protected] fmri=pkg:/[email protected]
                    close """,

                """
                    open [email protected],5.11-0
                    add depend type=exclude fmri=pkg:/C
                    close """,

                """
                    open [email protected],5.11-0
                    add depend type=exclude fmri=pkg:/B
                    close """,

                """
                    open [email protected],5.11-0.1
                    add depend type=require fmri=pkg:/incorp
                    close """,

                """
                    open [email protected],5.11-0.1
                    add depend type=require fmri=pkg:/incorp
                    close """,

                """
                    open [email protected],5.11-0.2
                    add depend type=require fmri=pkg:/incorp
                    close """,

                """
                    open [email protected],5.11-0.1
                    add depend type=incorporate [email protected],5.11-0.1
                    close """,

                 """
                    open [email protected],5.11-0.2
                    add depend type=incorporate [email protected],5.11-0.2
                    close """,

                """
                    open [email protected],5.11-0.1.1.0
                    add depend type=require fmri=pkg:/incorp
                    add depend type=require fmri=pkg:/idr1
                    close """,


                """
                    open [email protected],5.11-0.1.1.1
                    add depend type=require fmri=pkg:/incorp
                    add depend type=require fmri=pkg:/idr1
                    close """,

                """
                    open [email protected],5.11-0.1.2.0
                    add depend type=require fmri=pkg:/incorp
                    add depend type=require fmri=pkg:/idr2
                    close """,

                """
                    open [email protected],5.11-0.1.1.0
                    add depend type=require fmri=pkg:/incorp
                    add depend type=require fmri=pkg:/idrX
                    close """,

                """
                    open [email protected],5.11-0.1.1.0
                    add depend type=incorporate [email protected],5.11-0.1.1.0
                    add depend type=require fmri=idr1_entitlement
                    close """,

                """
                    open [email protected],5.11-0.1.1.1
                    add depend type=incorporate [email protected],5.11-0.1.1.1
                    add depend type=require fmri=idr1_entitlement
                    close """,

                """
                    open [email protected],5.11-0.1.2.0
                    add depend type=incorporate [email protected],5.11-0.1.2.0
                    add depend type=require fmri=idr2_entitlement
                    close """,

                """
                    open [email protected],5.11-0.1.1.0
                    add set name=pkg.additional-update-on-uninstall [email protected],5.11-0.1
                    add depend type=incorporate [email protected],5.11-0.1.1.0
                    add depend type=require fmri=idr1_entitlement
                    close """,

                """
                    open [email protected],5.11-0
                    add depend type=exclude fmri=no-idrs
                    close """,

                """
                    open [email protected],5.11-0
                    add depend type=exclude fmri=no-idrs
                    close """,

                # hack to prevent idrs from being installed from repo...

                """
                    open [email protected],5.11-0
                    close """,

                """
                    open pkg://contrib/[email protected],5.11-0
                    add depend type=exclude fmri=A
                    add depend type=require fmri=bogus1
                    add depend type=require fmri=bogus2
                    close """,

                """
                    open pkg://contrib/[email protected],5.11-0
                    add depend type=exclude fmri=B
                    close """,

                """
                    open pkg://contrib/[email protected],5.11-0
                    add depend type=exclude fmri=C
                    close """

                )


        def setUp(self):
                pkg5unittest.SingleDepotTestCase.setUp(self)
                self.pkgsend_bulk(self.rurl, self.pkgs)

        def test_install(self):
                self.image_create(self.rurl, prefix="")
                # simple test of reject
                self.pkg("install --reject B A")
                self.pkg("list A C")
                self.pkg("uninstall '*'")
                self.pkg("install --reject C A")
                self.pkg("list A B")
                self.pkg("uninstall '*'")

                # test swapping XOR'd pkgs B & C w/o uninstalling A
                self.pkg("install B")
                self.pkg("install A")
                self.pkg("list A B")
                self.pkg("install --reject B C")
                self.pkg("list A C")
                self.pkg("uninstall '*'")

                # test that solver picks up on impossible cases
                self.pkg("install -v --reject B --reject C A", exit=1)

                # test that publisher matching works
                self.pkg("install bogus")
                self.pkg("list bogus")
                self.pkg("install --reject B --reject 'pkg://contrib/*' A")

                # verify that matching accounts for reject.
                self.pkg("uninstall '*'")
                self.pkg("install -v --reject A A", exit=1)
                self.pkg("install -v --reject 'idr*' --reject 'bogus*' "
                    "--reject B '*'")
                self.pkg("list 'idr*' 'bogus*' B", exit=1)
                self.pkg("list A C incorp kernel no-idrs")

        def test_exact_install(self):
                """Test that the --reject option performs as expected."""

                self.image_create(self.rurl, prefix="")

                # test basic usage of --reject
                self.pkg("exact-install --reject B A")
                self.pkg("list A C")
                self.pkg("uninstall '*'")

                # test swapping XOR'd pkgs B & C.
                self.pkg("install B")
                self.pkg("install A")
                self.pkg("list A B")
                self.pkg("exact-install --reject B A")
                self.pkg("list A C")
                self.pkg("list B", exit=1)
                self.pkg("uninstall '*'")

                # test that solver picks up on impossible cases fails
                self.pkg("exact-install -v --reject B --reject C A", exit=1)

                # test that publisher matching works.
                self.pkg("install bogus")
                self.pkg("list bogus")
                self.pkg("exact-install --reject B --reject "
                    "'pkg://contrib/*' A")
                self.pkg("uninstall '*'")

                # verify that matching accounts for reject with --exact option.
                self.pkg("exact-install -v --reject A A", exit=1)
                self.pkg("exact-install -v --reject 'idr*' --reject 'bogus*' "
                    "--reject B '*'")
                self.pkg("list 'idr*' 'bogus*' B", exit=1)
                self.pkg("list A C incorp kernel no-idrs")

        def test_idr(self):
                self.image_create(self.rurl)
                # install kernel pkg; remember version so we can reinstall it later
                self.pkg("install no-idrs")
                self.pkg("install -v [email protected],5.11-0.1")
                self.pkg("list -Hv [email protected],5.11-0.1 | /usr/bin/awk '{print $1}'")
                kernel_fmri = self.output
                # upgrade to next version w/o encountering idrs
                self.pkg("update -v");
                self.pkg("list [email protected],5.11-0.2")
                self.pkg("list")

                # try installing idr1; testing wild card support as well
                self.pkg("uninstall no-idrs")
                self.pkg("install --reject 'k*' --reject 'i*'  no-idrs")
                self.pkg("install -v [email protected],5.11-0.1")
                self.pkg("install -v --reject no-idrs idr1_entitlement")
                self.pkg("install -v [email protected],5.11-0.1.1.0")
                self.pkg("update -v --reject idr2")
                self.pkg("list [email protected],5.11-0.1.1.1")

                # switch to idr2, which affects same package
                self.pkg("install -v --reject idr1 --reject 'idr1_*' idr2 idr2_entitlement")

                # switch back to base version of kernel
                self.pkg("update -v --reject idr2 --reject 'idr2_*' %s" % kernel_fmri)

                # reinstall idr1, then update to version 2 of base kernel
                self.pkg("install -v [email protected],5.11-0.1.1.0 idr1_entitlement")
                self.pkg("list [email protected],5.11-0.1.1.0")
                # Wildcards are purposefully used here for both patterns to
                # ensure pattern matching works as expected for update.
                self.pkg("update -v --reject 'idr1*' '*[email protected]'")
                self.pkg("list  [email protected],5.11-0.2")

        def test_idr_removal(self):
                """IDR removal with pkg actuators."""
                self.image_create(self.rurl)
                self.pkg("install no-idrs")
                self.pkg("install -v [email protected],5.11-0.1")
                self.pkg("list [email protected],5.11-0.1")

                # try installing idr
                self.pkg("install -v --reject no-idrs idr1_entitlement")
                self.pkg("install -v [email protected],5.11-0.1.1.0")
                # check if IDR pkgs got installed
                self.pkg("list [email protected],5.11-0.1.1.0")
                self.pkg("list [email protected],5.11-0.1.1.0")

                # uninstall IDR
                self.pkg("uninstall -v [email protected],5.11-0.1.1.0")
                self.pkg("list [email protected],5.11-0.1")

                # try with reject
                self.pkg("install -v [email protected],5.11-0.1.1.0")
                self.pkg("list [email protected],5.11-0.1.1.0")
                self.pkg("list [email protected],5.11-0.1.1.0")
                self.pkg("install --reject idrX B")
                self.pkg("list [email protected],5.11-0.1")

        def test_update(self):
                self.image_create(self.rurl)
                # Test update reject without wildcards.
                self.pkg("install  [email protected],5.11-0.1.1.0 [email protected],5.11-0")
                self.pkg("update -v --reject A")
                self.pkg("list A", exit=1)
                self.pkg("verify")

                # Reinstall kernel package, install A, and test update again using
                # wildcards.
                self.pkg("uninstall '*'")
                self.pkg("install [email protected],5.11-0.1.1.0")
                self.pkg("list [email protected],5.11-0.1.1.0")
                self.pkg("install [email protected],5.11-0")
                self.pkg("update -v --reject A '*'")
                self.pkg("list A", exit=1)
                self.pkg("list [email protected],5.11-0.1.1.1")
                self.pkg("verify")


class TestPkgInstallAmbiguousPatterns(pkg5unittest.SingleDepotTestCase):

        # An "ambiguous" package name pattern is one which, because of the
        # pattern matching rules, might refer to more than one package.  This
        # may be as obvious as the pattern "SUNW*", but also like the pattern
        # "foo", where "foo" and "a/foo" both exist in the catalog.

        afoo10 = """
            open a/[email protected],5.11-0
            close """

        bfoo10 = """
            open b/[email protected],5.11-0
            close """

        bar10 = """
            open [email protected],5.11-0
            close """

        foo10 = """
            open [email protected],5.11-0
            close """

        foo11 = """
            open [email protected],5.11-0
            close """

        anotherfoo11 = """
            open another/[email protected],5.11-0
            close """

        depender10 = """
            open [email protected],5.11-0
            add depend type=require [email protected]
            close """

        depender11 = """
            open [email protected],5.11-0
            add depend type=require [email protected]
            close """

        def test_bug_4204(self):
                """Don't stack trace when printing a PlanCreationException with
                "multiple_matches" populated (on uninstall)."""

                self.pkgsend_bulk(self.rurl, (self.afoo10, self.bfoo10,
                    self.bar10))
                self.image_create(self.rurl)

                self.pkg("install foo", exit=1)
                self.pkg("install a/foo b/foo", exit=0)
                self.pkg("list")
                self.pkg("uninstall foo", exit=1)
                self.pkg("uninstall a/foo b/foo", exit=0)

        def test_bug_6874(self):
                """Don't stack trace when printing a PlanCreationException with
                "multiple_matches" populated (on install and update)."""

                self.pkgsend_bulk(self.rurl, (self.afoo10, self.bfoo10))
                self.image_create(self.rurl)

                self.pkg("install foo", exit=1)

        def test_ambiguous_pattern_install(self):
                """An update should never get confused about an existing
                package being part of an ambiguous set of package names."""

                self.pkgsend_bulk(self.rurl, self.foo10)

                self.image_create(self.rurl)
                self.pkg("install foo")

                self.pkgsend_bulk(self.rurl, self.anotherfoo11)
                self.pkg("refresh")
                self.pkg("update -v", exit=4)

        def test_ambiguous_pattern_depend(self):
                """A dependency on a package should pull in an exact name
                match."""

                self.ambiguous_pattern_depend_helper("install")
                self.ambiguous_pattern_depend_helper("exact-install")

        def ambiguous_pattern_depend_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, (self.depender10, self.foo10))

                self.image_create(self.rurl)
                self.pkg("%s depender" % install_cmd)

                self.pkgsend_bulk(self.rurl, (self.foo11, self.anotherfoo11,
                    self.depender11))
                self.pkg("refresh")

                self.pkg("%s depender" % install_cmd)

                # Make sure that we didn't get other/foo from the dependency.
                self.pkg("list another/foo", exit=1)

        def test_non_ambiguous_fragment(self):
                """We should be able to refer to a package by its "basename", if
                that component is unique."""

                self.non_ambiguous_fragment_helper("install")
                self.non_ambiguous_fragment_helper("exact-install")

        def non_ambiguous_fragment_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, self.anotherfoo11)
                self.image_create(self.rurl)

                # Right now, this is not exact, but still unambiguous
                self.pkg("%s foo" % install_cmd)

                # Create ambiguity
                self.pkgsend_bulk(self.rurl, self.foo11)
                self.pkg("refresh")

                # This is unambiguous, should succeed
                self.pkg("%s pkg:/foo" % install_cmd)

                # This is now ambiguous, should fail
                self.pkg("%s foo" % install_cmd, exit=1)
                self.pkgrepo("remove -s %s pkg:/[email protected]" % self.rurl )


class TestPkgInstallOverlappingPatterns(pkg5unittest.SingleDepotTestCase):

        a_1 = """
            open [email protected],5.11-0
            close """

        pub2_a_1 = """
            open pkg://pub2/[email protected],5.11-0
            close """

        a_11 = """
            open [email protected],5.11-0
            close """

        a_2 = """
            open [email protected],5.11-0
            close """

        pub2_a_2 = """
            open pkg://pub2/[email protected],5.11-0
            close """

        a_3 = """
            open [email protected],5.11-0
            close """

        aa_1 = """
            open [email protected],5.11-0
            close """

        afoo_1 = """
            open a/[email protected],5.11-0
            close """

        bfoo_1 = """
            open b/[email protected],5.11-0
            close """

        fooa_1 = """
            open foo/[email protected],5.11-0
            close """

        foob_1 = """
            open foo/[email protected],5.11-0
            close """

        def test_overlapping_one_package_available(self):
                self.pkgsend_bulk(self.rurl, self.a_1)
                api_inst = self.image_create(self.rurl)

                self._api_install(api_inst, ["a@1", "a@1"], noexecute=True)
                self._api_install(api_inst, ["a@1", "[email protected]"], noexecute=True)
                self._api_install(api_inst, ["a@1", "pkg://test/a@1"],
                    noexecute=True)
                self._api_install(api_inst, ["a*@1", "pkg:/*a@1"],
                    noexecute=True)
                self._api_install(api_inst, ["a*@1", "a@1"], noexecute=True)
                self._api_install(api_inst, ["a@1", "pkg://test/a*@1"],
                    noexecute=True)
                # This fails because a*@2 matches no patterns on its own.
                self.pkg("install -n 'a*@2' 'a@1'", exit=1)

        def test_overlapping_conflicting_versions_no_wildcard_match(self):
                self.pkgsend_bulk(self.rurl, self.a_1 + self.a_2)
                api_inst = self.image_create(self.rurl)

                self.pkg("install -n a@1 a@2", exit=1)
                self.pkg("install -n a@2 pkg://test/a@1", exit=1)
                self.pkg("install -n 'a*@2' 'pkg:/*a@1'", exit=1)

                # This is allowed because a*@1 matches published packages, even
                # though the packages it matches aren't installed in the image.
                self._api_install(api_inst, ["a*@1", "a@2"])
                self.pkg("list a@2")
                self._api_uninstall(api_inst, ["a"])

                self._api_install(api_inst, ["a@1", "pkg://test/a*@2"])
                self.pkg("list a@1")
                self._api_uninstall(api_inst, ["a"])

                self.pkgsend_bulk(self.rurl, self.a_3)
                self._api_install(api_inst, ["a*@1", "*a@2", "a@3"])
                self.pkg("list a@3")
                self._api_uninstall(api_inst, ["a"])

                self._api_install(api_inst, ["a*@1", "*a@2", "a@latest"])
                self.pkg("list a@3")
                self._api_uninstall(api_inst, ["a"])

                self.pkgsend_bulk(self.rurl, self.a_11)
                self.pkg("install [email protected] [email protected]", exit=1)
                self._api_install(api_inst, ["a@1", "[email protected]", "a*@1.1"])
                self.pkg("list [email protected]")
                self._api_uninstall(api_inst, ["a"])

                self._api_install(api_inst, ["*", "[email protected]"])
                self.pkg("list [email protected]")
                self._api_uninstall(api_inst, ["a"])

        def test_overlapping_multiple_packages(self):
                self.pkgsend_bulk(self.rurl, self.a_1 + self.a_2 + self.aa_1 +
                    self.afoo_1 + self.bfoo_1 + self.fooa_1 + self.foob_1)
                api_inst = self.image_create(self.rurl)

                self.pkg("install '*a@1' 'a*@2'", exit=1)

                self._api_install(api_inst, ["a*@1", "a@2"])
                self.pkg("list -Hv")
                self.assertEqual(len(self.output.splitlines()), 3)
                self.assert_("a@2" in self.output)
                self._api_uninstall(api_inst, ["a", "aa", "a/foo"])

                self._api_install(api_inst, ["/a@1", "a*@2", "*foo*@1"])
                self.pkg("list -Hv")
                self.assertEqual(len(self.output.splitlines()), 5)
                self.assert_("a@1" in self.output)
                self._api_uninstall(api_inst,
                    ["/a", "a/foo", "b/foo", "foo/a", "foo/b"])

        def test_overlapping_multiple_publishers(self):
                self.pkgsend_bulk(self.rurl, self.a_1 + self.pub2_a_2)
                api_inst = self.image_create(self.rurl)

                self._api_install(api_inst, ["a*@1", "pkg://pub2/a@2"],
                    noexecute=True)
                self._api_install(api_inst, ["a@1", "pkg://pub2/a*@2"],
                    noexecute=True)
                self._api_install(api_inst, ["a@1", "pkg://pub2/*@2"],
                    noexecute=True)
                self.pkg("install -n 'pkg://test/a*@1' 'pkg://pub2/*a@2'",
                    exit=1)
                self.pkg("install -n 'pkg://test/a@1' 'pkg://pub2/a@2'",
                    exit=1)
                self.pkg("install -n 'a@1' 'pkg://pub2/a@2'", exit=1)

                self.pkgsend_bulk(self.rurl, self.pub2_a_1)
                self._api_install(api_inst, ["a@1", "pkg://pub2/a@1"])
                self.pkg("list -Hv 'pkg://pub2/*'")
                self.assertEqual(len(self.output.splitlines()), 1)
                self.assert_("a@1" in self.output)
                self._api_uninstall(api_inst, ["a"])

                self._api_install(api_inst, ["a@1", "pkg://test/a@1"])
                self.pkg("list -Hv 'pkg://test/*'")
                self.assertEqual(len(self.output.splitlines()), 1)
                self.assert_("a@1" in self.output)
                self._api_uninstall(api_inst, ["a"])

                self._api_install(api_inst, ["a*@1", "pkg://pub2/*@2"])
                self.pkg("list -Hv 'pkg://pub2/*'")
                self.assertEqual(len(self.output.splitlines()), 1)
                self.assert_("a@2" in self.output)
                self._api_uninstall(api_inst, ["a"])

                self._api_install(api_inst,
                    ["pkg://test/a@1", "pkg://pub2/*@2"])
                self.pkg("list -Hv 'pkg://test/*'")
                self.assertEqual(len(self.output.splitlines()), 1)
                self.assert_("a@1" in self.output)
                self._api_uninstall(api_inst, ["a"])

                # This intentionally doesn't use api_install to check for
                # special handling of '*' in client.py.
                self.pkg("install '*' 'pkg://pub2/*@2'")
                self.pkg("list -Hv 'pkg://pub2/*'")
                self.assertEqual(len(self.output.splitlines()), 1)
                self.assert_("a@2" in self.output)
                self._api_uninstall(api_inst, ["a"])


class TestPkgInstallCircularDependencies(pkg5unittest.SingleDepotTestCase):
        # Only start/stop the depot once (instead of for every test)
        persistent_setup = True

        pkg10 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/pkg2
            close
        """

        pkg20 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/pkg3
            close
        """

        pkg30 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/pkg1
            close
        """


        pkg11 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            close
        """

        pkg21 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            close
        """

        pkg31 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            close
        """

        def test_unanchored_circular_dependencies(self):
                """ check to make sure we can install or exact-install
                circular dependencies w/o versions
                """

                self.unanchored_circular_dependencies_helper("install")
                self.unanchored_circular_dependencies_helper("exact-install")

        def unanchored_circular_dependencies_helper(self, install_cmd):
                # Send 1.0 versions of packages.
                self.pkgsend_bulk(self.rurl, (self.pkg10, self.pkg20,
                    self.pkg30))

                self.image_create(self.rurl)
                self.pkg("%s pkg1" % install_cmd)
                self.pkg("list")
                self.pkg("verify -v")

        def test_anchored_circular_dependencies(self):
                """ check to make sure we can install or exact-install
                circular dependencies w/ versions
                """

                self.anchored_circular_dependencies_helper("install")
                self.unanchored_circular_dependencies_helper("exact-install")

        def anchored_circular_dependencies_helper(self, install_cmd):
                # Send 1.1 versions of packages.
                self.pkgsend_bulk(self.rurl, (self.pkg11, self.pkg21,
                    self.pkg31))

                self.image_create(self.rurl)
                self.pkg("%s pkg1" % install_cmd)
                self.pkg("list")
                self.pkg("verify -v")


class TestPkgInstallUpdateSolverOutput(pkg5unittest.SingleDepotTestCase):
        # Only start/stop the depot once (instead of for every test)
        persistent_setup = True

        octo10 = """
            open [email protected],5.11-0
            close
        """

        octo20 = """
            open [email protected],5.11-0
            close
        """

        incorp = """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            close
        """

        def test_output_two_issues(self):
                """ ^^^ hard to find a good name for this, it tests for bug
                21130996.
                In case one pkg triggers two or more issues, one of which is not
                considered print-worthy, we wouldn't print anything at all."""

                self.pkgsend_bulk(self.rurl,
                    (self.incorp, self.octo10, self.octo20))
                self.image_create(self.rurl)

                self.pkg("install incorp octo@2")
                self.pkg("install -v octo@1", exit=1)

                # Check that the root cause for the issue is shown;
                # the incorporation does not allow the older version.
                self.assertTrue("[email protected]" in self.errout,
                    "Excluding incorporation not shown in solver error.")
                # Check that the notice about a newer version already installed
                # is ommited (it's not relevant).
                self.assertFalse("[email protected]" in self.errout,
                    "Newer version should not be shown in solver error.")


class TestPkgInstallUpgrade(_TestHelper, pkg5unittest.SingleDepotTestCase):
        # Only start/stop the depot once (instead of for every test)
        persistent_setup = True
        need_ro_data = True

        incorp10 = """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            close
        """

        incorp20 = """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            close
        """

        incorp30 = """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            close
        """

        incorpA = """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            close
        """

        incorpB =  """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            close
        """

        iridium10 = """
            open [email protected],5.11-0
            add depend fmri=pkg:/[email protected] type=require
            close
        """
        amber10 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=/lib
            add dir mode=0755 owner=root group=bin path=/etc
            add file tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.so.1
            add link path=/lib/libc.symlink target=/lib/libc.so.1
            add hardlink path=/lib/libc.hardlink target=/lib/libc.so.1
            add file tmp/amber1 mode=0444 owner=root group=bin path=/etc/amber1
            add file tmp/amber2 mode=0444 owner=root group=bin path=/etc/amber2
            add license tmp/copyright1 license=copyright_ä
            close
        """

        brass10 = """
            open [email protected],5.11-0
            add depend fmri=pkg:/bronze type=require
            close
        """

        bronze10 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=/usr
            add dir mode=0755 owner=root group=bin path=/usr/bin
            add file tmp/sh mode=0555 owner=root group=bin path=/usr/bin/sh
            add link path=/usr/bin/jsh target=./sh
            add hardlink path=/lib/libc.bronze target=/lib/libc.so.1
            add file tmp/bronze1 mode=0444 owner=root group=bin path=/etc/bronze1
            add file tmp/bronze2 mode=0444 owner=root group=bin path=/etc/bronze2
            add file tmp/bronzeA1 mode=0444 owner=root group=bin path=/A/B/C/D/E/F/bronzeA1
            add depend fmri=pkg:/[email protected] type=require
            add license tmp/copyright2 license=copyright
            close
        """

        amber20 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=/usr
            add dir mode=0755 owner=root group=bin path=/usr/bin
            add file tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.so.1
            add link path=/lib/libc.symlink target=/lib/libc.so.1
            add hardlink path=/lib/libc.amber target=/lib/libc.bronze
            add hardlink path=/lib/libc.hardlink target=/lib/libc.so.1
            add file tmp/amber1 mode=0444 owner=root group=bin path=/etc/amber1
            add file tmp/amber2 mode=0444 owner=root group=bin path=/etc/bronze2
            add depend fmri=pkg:/[email protected] type=require
            add license tmp/copyright2 license=copyright
            close
        """

        bronze20 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=/etc
            add dir mode=0755 owner=root group=bin path=/lib
            add file tmp/sh mode=0555 owner=root group=bin path=/usr/bin/sh
            add file tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.bronze
            add link path=/usr/bin/jsh target=./sh
            add hardlink path=/lib/libc.bronze2.0.hardlink target=/lib/libc.so.1
            add file tmp/bronze1 mode=0444 owner=root group=bin path=/etc/bronze1
            add file tmp/bronze2 mode=0444 owner=root group=bin path=/etc/amber2
            add license tmp/copyright3 license=copyright
            add file tmp/bronzeA2 mode=0444 owner=root group=bin path=/A1/B2/C3/D4/E5/F6/bronzeA2
            add depend fmri=pkg:/[email protected] type=require
            close
        """

        bronze30 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=/etc
            add dir mode=0755 owner=root group=bin path=/lib
            add file tmp/sh mode=0555 owner=root group=bin path=/usr/bin/sh
            add file tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.bronze
            add link path=/usr/bin/jsh target=./sh
            add hardlink path=/lib/libc.bronze2.0.hardlink target=/lib/libc.so.1
            add file tmp/bronze1 mode=0444 owner=root group=bin path=/etc/bronze1
            add file tmp/bronze2 mode=0444 owner=root group=bin path=/etc/amber2
            add license tmp/copyright3 license=copyright
            add file tmp/bronzeA2 mode=0444 owner=root group=bin path=/A1/B2/C3/D4/E5/F6/bronzeA2
            add depend fmri=pkg:/[email protected] type=require
            close
        """


        gold10 = """
            open [email protected],5.11-0
            add file tmp/gold-passwd1 mode=0644 owner=root group=bin path=etc/passwd preserve=true
            add file tmp/gold-group mode=0644 owner=root group=bin path=etc/group preserve=true
            add file tmp/gold-shadow mode=0600 owner=root group=bin path=etc/shadow preserve=true
            add file tmp/gold-ftpusers mode=0644 owner=root group=bin path=etc/ftpd/ftpusers preserve=true
            add file tmp/gold-silly mode=0644 owner=root group=bin path=etc/silly
            add file tmp/gold-silly mode=0644 owner=root group=bin path=etc/silly2
            close
        """

        gold20 = """
            open [email protected],5.11-0
            add file tmp/config2 mode=0644 owner=root group=bin path=etc/config2 original_name="gold:etc/passwd" preserve=true
            close
        """

        gold30 = """
            open [email protected],5.11-0
            close
        """

        golduser10 = """
            open [email protected]
            add user username=Kermit group=adm home-dir=/export/home/Kermit
            close
        """

        golduser20 = """
            open [email protected]
            close
        """

        silver10  = """
            open [email protected],5.11-0
            close
        """

        silver20  = """
            open [email protected],5.11-0
            add file tmp/gold-passwd2 mode=0644 owner=root group=bin path=etc/passwd original_name="gold:etc/passwd" preserve=true
            add file tmp/gold-group mode=0644 owner=root group=bin path=etc/group original_name="gold:etc/group" preserve=true
            add file tmp/gold-shadow mode=0600 owner=root group=bin path=etc/shadow original_name="gold:etc/shadow" preserve=true
            add file tmp/gold-ftpusers mode=0644 owner=root group=bin path=etc/ftpd/ftpusers original_name="gold:etc/ftpd/ftpusers" preserve=true
            add file tmp/gold-silly mode=0644 owner=root group=bin path=etc/silly
            add file tmp/silver-silly mode=0644 owner=root group=bin path=etc/silly2
            close
        """
        silver30  = """
            open [email protected],5.11-0
            add file tmp/config2 mode=0644 owner=root group=bin path=etc/config2 original_name="gold:etc/passwd" preserve=true
            close
        """

        silveruser = """
            open [email protected]
            add user username=Kermit group=adm home-dir=/export/home/Kermit
            close
        """


        iron10 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=etc
            add file tmp/config1 mode=0644 owner=root group=bin path=etc/foo
            add hardlink path=etc/foo.link target=foo
            add license tmp/copyright1 license=copyright
            close
        """
        iron20 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=etc
            add file tmp/config2 mode=0644 owner=root group=bin path=etc/foo
            add hardlink path=etc/foo.link target=foo
            add license tmp/copyright2 license=copyright
            close
        """

        concorp10 = """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            close
        """

        dricon1 = """
            open dricon@1
            add dir path=var mode=755 owner=root group=root
            add dir path=var/run mode=755 owner=root group=root
            add dir mode=0755 owner=root group=root path=system
            add dir mode=0755 owner=root group=root path=system/volatile
            add dir path=/tmp mode=755 owner=root group=root
            add dir path=/etc mode=755 owner=root group=root
            add file tmp/dricon_da path=/etc/driver_aliases mode=644 owner=root group=sys preserve=true
            add file tmp/dricon_n2m path=/etc/name_to_major mode=644 owner=root group=sys preserve=true
            close
        """

        dricon2 = """
            open dricon@2
            add dir path=var mode=755 owner=root group=root
            add dir path=var/run mode=755 owner=root group=root
            add dir mode=0755 owner=root group=root path=system
            add dir mode=0755 owner=root group=root path=system/volatile
            add dir path=/tmp mode=755 owner=root group=root
            add dir path=/etc mode=755 owner=root group=root
            add file tmp/dricon2_da path=/etc/driver_aliases mode=644 owner=root group=sys preserve=true
            add file tmp/dricon_n2m path=/etc/name_to_major mode=644 owner=root group=sys preserve=true
            add driver name=zigit alias=pci8086,1234
            close
        """

        dricon3 = """
            open dricon@3
            add dir path=var mode=755 owner=root group=root
            add dir path=var/run mode=755 owner=root group=root
            add dir path=/tmp mode=755 owner=root group=root
            add dir path=/etc mode=755 owner=root group=root
            add file tmp/dricon2_da path=/etc/driver_aliases mode=644 owner=root group=sys preserve=true
            add file tmp/dricon_n2m path=/etc/name_to_major mode=644 owner=root group=sys preserve=true
            add driver name=zigit alias=pci8086,1234
            add driver name=figit alias=pci8086,1234
            close
        """

        dripol1 = """
            open dripol@1
            add dir path=var mode=755 owner=root group=root
            add dir path=var/run mode=755 owner=root group=root
            add dir mode=0755 owner=root group=root path=system
            add dir mode=0755 owner=root group=root path=system/volatile
            add dir path=/tmp mode=755 owner=root group=root
            add dir path=/etc mode=755 owner=root group=root
            add dir path=/etc/security mode=755 owner=root group=root
            add file tmp/dricon2_da path=/etc/driver_aliases mode=644 owner=root group=sys preserve=true
            add file tmp/dricon_n2m path=/etc/name_to_major mode=644 owner=root group=sys preserve=true
            add file tmp/dripol1_dp path=/etc/security/device_policy mode=644 owner=root group=sys preserve=true
            add driver name=frigit policy="read_priv_set=net_rawaccess write_priv_set=net_rawaccess tpd_member=true"
            close
        """

        dripol2 = """
            open dripol@2
            add dir path=var mode=755 owner=root group=root
            add dir path=var/run mode=755 owner=root group=root
            add dir mode=0755 owner=root group=root path=system
            add dir mode=0755 owner=root group=root path=system/volatile
            add dir path=/tmp mode=755 owner=root group=root
            add dir path=/etc mode=755 owner=root group=root
            add dir path=/etc/security mode=755 owner=root group=root
            add file tmp/dricon2_da path=/etc/driver_aliases mode=644 owner=root group=sys preserve=true
            add file tmp/dricon_n2m path=/etc/name_to_major mode=644 owner=root group=sys preserve=true
            add file tmp/dripol1_dp path=/etc/security/device_policy mode=644 owner=root group=sys preserve=true
            add driver name=frigit
            close
        """

        dripol3 = """
            open dripol@3
            add dir path=var mode=755 owner=root group=root
            add dir path=var/run mode=755 owner=root group=root
            add dir mode=0755 owner=root group=root path=system
            add dir mode=0755 owner=root group=root path=system/volatile
            add dir path=/tmp mode=755 owner=root group=root
            add dir path=/etc mode=755 owner=root group=root
            add dir path=/etc/security mode=755 owner=root group=root
            add file tmp/dricon2_da path=/etc/driver_aliases mode=644 owner=root group=sys preserve=true
            add file tmp/dricon_n2m path=/etc/name_to_major mode=644 owner=root group=sys preserve=true
            add file tmp/dripol1_dp path=/etc/security/device_policy mode=644 owner=root group=sys preserve=true
            add driver name=frigit policy="tpd_member=true"
            close
        """

        dripol4 = """
            open dripol@4
            add dir path=var mode=755 owner=root group=root
            add dir path=var/run mode=755 owner=root group=root
            add dir mode=0755 owner=root group=root path=system
            add dir mode=0755 owner=root group=root path=system/volatile
            add dir path=/tmp mode=755 owner=root group=root
            add dir path=/etc mode=755 owner=root group=root
            add dir path=/etc/security mode=755 owner=root group=root
            add file tmp/dricon2_da path=/etc/driver_aliases mode=644 owner=root group=sys preserve=true
            add file tmp/dricon_n2m path=/etc/name_to_major mode=644 owner=root group=sys preserve=true
            add file tmp/dripol1_dp path=/etc/security/device_policy mode=644 owner=root group=sys preserve=true
            add driver name=frigit
            close
        """

        dripol5 = """
            open dripol@5
            add dir path=var mode=755 owner=root group=root
            add dir path=var/run mode=755 owner=root group=root
            add dir mode=0755 owner=root group=root path=system
            add dir mode=0755 owner=root group=root path=system/volatile
            add dir path=/tmp mode=755 owner=root group=root
            add dir path=/etc mode=755 owner=root group=root
            add dir path=/etc/security mode=755 owner=root group=root
            add file tmp/dricon2_da path=/etc/driver_aliases mode=644 owner=root group=sys preserve=true
            add file tmp/dricon_n2m path=/etc/name_to_major mode=644 owner=root group=sys preserve=true
            add file tmp/dripol1_dp path=/etc/security/device_policy mode=644 owner=root group=sys preserve=true
            add driver name=frigit perms="node1 0666 root sys" policy="node1 read_priv_set=all write_priv_set=all tpd_member=true"
            close
        """

        liveroot10 = """
            open [email protected]
            add dir path=/etc mode=755 owner=root group=root
            add file tmp/liveroot1 path=/etc/liveroot mode=644 owner=root group=sys reboot-needed=true
            close
        """
        liveroot20 = """
            open [email protected]
            add dir path=/etc mode=755 owner=root group=root
            add file tmp/liveroot2 path=/etc/liveroot mode=644 owner=root group=sys reboot-needed=true
            close
        """

        renameold1 = """
            open [email protected]
            add file tmp/renold1 path=testme mode=0644 owner=root group=root preserve=renameold
            close
        """

        renameold2 = """
            open [email protected]
            add file tmp/renold1 path=testme mode=0640 owner=root group=root preserve=renameold
            close
        """

        renameold3 = """
            open [email protected]
            add file tmp/renold3 path=testme mode=0644 owner=root group=root preserve=renameold
            close
        """

        renamenew1 = """
            open [email protected]
            add file tmp/rennew1 path=testme mode=0644 owner=root group=root preserve=renamenew
            close
        """

        renamenew2 = """
            open [email protected]
            add file tmp/rennew1 path=testme mode=0640 owner=root group=root preserve=renamenew
            close
        """

        renamenew3 = """
            open [email protected]
            add file tmp/rennew3 path=testme mode=0644 owner=root group=root preserve=renamenew
            close
        """

        preserve1 = """
            open [email protected]
            add file tmp/preserve1 path=testme mode=0644 owner=root group=root preserve=true
            close
        """

        preserve2 = """
            open [email protected]
            add file tmp/preserve1 path=testme mode=0640 owner=root group=root preserve=true
            close
        """

        preserve3 = """
            open [email protected]
            add file tmp/preserve3 path=testme mode=0644 owner=root group=root preserve=true
            close
        """

        preslegacy = """
            open [email protected]
            add file tmp/preserve1 path=testme mode=0644 owner=root group=root preserve=true
            close
            open [email protected]
            add file tmp/preserve2 path=testme mode=0444 owner=root group=root preserve=legacy
            close
            open [email protected]
            add file tmp/preserve3 path=testme mode=0444 owner=root group=root preserve=legacy
            close
        """

        renpreslegacy = """
            open [email protected]
            add file tmp/preserve1 path=testme mode=0644 owner=root group=root preserve=true
            close
            open [email protected]
            add set pkg.renamed=true
            add depend type=require [email protected]
            close
            open [email protected]
            add file tmp/preserve2 path=newme mode=0444 owner=root group=root preserve=legacy original_name=orig_preslegacy:testme
            close
        """

        presabandon = """
            open [email protected]
            add file tmp/preserve1 path=testme mode=0444 owner=root group=root preserve=true
            close
            open [email protected]
            add file tmp/preserve1 path=testme mode=0644  owner=root group=root preserve=abandon
            close
            open [email protected]
            add file tmp/preserve3 path=testme mode=0444  owner=root group=root preserve=abandon
            close
            open [email protected]
            add file tmp/preserve3 path=testme mode=0644  owner=root group=root preserve=true
            close
        """

        renpreserve = """
            open [email protected]
            add file tmp/preserve1 path=foo1 mode=0644 owner=root group=root preserve=true
            add file tmp/bronze1 path=bronze1 mode=0644 owner=root group=root preserve=true
            close
            open [email protected]
            add set pkg.renamed=true
            add depend type=require [email protected]
            close
            open [email protected]
            add file tmp/foo2 path=foo2 mode=0644 owner=root group=root original_name=orig_pkg:foo1 preserve=true
            add file tmp/bronze1 path=bronze1 mode=0644 owner=root group=root preserve=true
            close
        """

        linkpreserve = """
            open [email protected]
            add file tmp/preserve1 path=etc/ssh/sshd_config mode=0644 owner=root group=root preserve=true
            close
            open [email protected]
            add file tmp/preserve2 path=etc/sunssh/sshd_config mode=0644 owner=root group=root preserve=true original_name=linkpreserve:etc/ssh/sshd_config
            add link path=etc/ssh/sshd_config target=../sunssh/sshd_config
            close """

        salvage = """
            open [email protected]
            add dir path=var mode=755 owner=root group=root
            add dir path=var/mail mode=755 owner=root group=root
            add dir path=var/log mode=755 owner=root group=root
            add dir path=var/noodles mode=755 owner=root group=root
            add dir path=var/persistent mode=755 owner=root group=root
            close
            open [email protected]
            add dir path=var mode=755 owner=root group=root
            add dir path=var/.migrate-to-shared mode=755 owner=root group=root
            add dir path=var/.migrate-to-shared/mail salvage-from=var/mail mode=755 owner=root group=root
            add dir path=var/.migrate-to-shared/log salvage-from=var/log mode=755 owner=root group=root
            add dir path=var/spaghetti mode=755 owner=root group=root
            add dir path=var/persistent mode=755 owner=root group=root salvage-from=var/noodles
            close
            open [email protected]
            add dir path=var mode=755 owner=root group=root
            add dir path=var/.migrate-to-shared mode=755 owner=root group=root
            add dir path=var/.migrate-to-shared/mail salvage-from=var/mail mode=755 owner=root group=root
            add dir path=var/.migrate-to-shared/log salvage-from=var/log mode=755 owner=root group=root
            add dir path=var/persistent mode=755 owner=root group=root salvage-from=var/noodles salvage-from=var/spaghetti
            close
        """

        salvage_special = """
            open [email protected]
            add dir path=salvage mode=755 owner=root group=root
            close
        """

        dumdir10 = """
            open [email protected]
            add dir path=etc mode=0755 owner=root group=bin
            add file tmp/amber1 mode=0755 owner=root group=bin path=etc/amber1
            close
        """

        dumdir20 = """
            open [email protected]
            add dir path=etc mode=0700 owner=root group=bin
            add file tmp/amber1 mode=0444 owner=root group=bin path=etc/amber1
            close
        """

        misc_files1 = [
            "tmp/amber1", "tmp/amber2", "tmp/bronzeA1",  "tmp/bronzeA2",
            "tmp/bronze1", "tmp/bronze2",
            "tmp/copyright1", "tmp/copyright2",
            "tmp/copyright3", "tmp/copyright4",
            "tmp/libc.so.1", "tmp/sh", "tmp/config1", "tmp/config2",
            "tmp/gold-passwd1", "tmp/gold-passwd2", "tmp/gold-group",
            "tmp/gold-shadow", "tmp/gold-ftpusers", "tmp/gold-silly",
            "tmp/silver-silly", "tmp/preserve1", "tmp/preserve2",
            "tmp/preserve3", "tmp/renold1", "tmp/renold3", "tmp/rennew1",
            "tmp/rennew3", "tmp/liveroot1", "tmp/liveroot2", "tmp/foo2",
        ]

        misc_files2 = {
            "tmp/dricon_da": """\
wigit "pci8086,1234"
wigit "pci8086,4321"
# someother "pci8086,1234"
foobar "pci8086,9999"
""",
            "tmp/dricon2_da": """\
zigit "pci8086,1234"
wigit "pci8086,4321"
# someother "pci8086,1234"
foobar "pci8086,9999"
""",
            "tmp/dricon_n2m": """\
wigit 1
foobar 2
""",
            "tmp/dripol1_dp": """\
*               read_priv_set=none              write_priv_set=none
""",
            "tmp/gold-passwd1": """\
root:x:0:0::/root:/usr/bin/bash
daemon:x:1:1::/:
bin:x:2:2::/usr/bin:
sys:x:3:3::/:
adm:x:4:4:Admin:/var/adm:
""",
            "tmp/gold-passwd2": """\
root:x:0:0::/root:/usr/bin/bash
daemon:x:1:1::/:
bin:x:2:2::/usr/bin:
sys:x:3:3::/:
adm:x:4:4:Admin:/var/adm:
bogus:x:10001:10001:Bogus User:/:
""",
            "tmp/gold-group": """\
root::0:
other::1:root
bin::2:root,daemon
sys::3:root,bin,adm
adm::4:root,daemon
""",
            "tmp/gold-shadow": """\
root:9EIfTNBp9elws:13817::::::
daemon:NP:6445::::::
bin:NP:6445::::::
sys:NP:6445::::::
adm:NP:6445::::::
""",
            "tmp/gold-ftpusers": """\
root
bin
sys
adm
""",
        }

        cat_data = " "

        foo10 = """
            open [email protected],5.11-0
            close """

        only_attr10 = """
            open [email protected],5.11-0
            add set name=foo value=bar
            close """

        def setUp(self):
                pkg5unittest.SingleDepotTestCase.setUp(self)
                self.make_misc_files(self.misc_files1)
                self.make_misc_files(self.misc_files2)

        def __salvage_file_contains(self, sroot, fprefix, entry):
                salvaged = [
                    n for n in os.listdir(sroot)
                    if n.startswith(fprefix + "-")
                ]

                sfile = os.path.join(sroot, salvaged[0])
                with open(sfile, "rb") as f:
                        found = [l.strip() for l in f if entry in l]
                        self.assertEqual(found, [entry])

        def test_incorp_install(self):
                """Make sure we don't round up packages we specify on
                install"""

                first_bronze = self.pkgsend_bulk(self.rurl, self.bronze20)[0]
                self.pkgsend_bulk(self.rurl, (self.incorp20, self.amber10,
                    self.bronze10, self.amber20, self.bronze20))

                # create image
                self.image_create(self.rurl)
                # install incorp2
                self.pkg("install [email protected]")
                # try to install version 1
                self.pkg("install [email protected]", exit=1)
                # install earliest version [email protected]
                self.pkg("install %s" % first_bronze)
                self.pkg("list -v %s" % first_bronze)
                self.pkg("install [email protected]")

        def test_upgrade1(self):

                """ Upgrade torture test.
                    Send package [email protected], bronze1.0; install bronze1.0, which
                    should cause amber to also install.
                    Send 2.0 versions of packages which contains a lot of
                    complex transactions between amber and bronze, then do
                    an update, and try to check the results.
                """

                # Send 1.0 versions of packages.
                self.pkgsend_bulk(self.rurl, (self.incorp10, self.amber10,
                    self.bronze10))

                #
                # In version 2.0, several things happen:
                #
                # Amber and Bronze swap a file with each other in both
                # directions.  The dependency flips over (Amber now depends
                # on Bronze).  Amber and Bronze swap ownership of various
                # directories.
                #
                # Bronze's 1.0 hardlink to amber's libc goes away and is
                # replaced with a file of the same name.  Amber hardlinks
                # to that.
                #
                self.pkgsend_bulk(self.rurl, (self.incorp20, self.amber20,
                    self.bronze20))

                # create image and install version 1
                self.image_create(self.rurl)
                self.pkg("install [email protected]")
                self.file_exists(".SELF-ASSEMBLY-REQUIRED")
                self.pkg("install bronze")

                self.pkg("list [email protected] [email protected]")
                self.pkg("verify -v")

                # demonstrate that [email protected] prevents package movement
                self.pkg("install [email protected] [email protected]", exit=1)

                # ...again, but using @latest.
                self.pkg("install bronze@latest amber@latest", exit=1)
                self.pkg("update bronze@latest amber@latest", exit=1)

                # Now update to get new versions of amber and bronze
                self.file_remove(".SELF-ASSEMBLY-REQUIRED")
                self.pkg("update")
                self.file_exists(".SELF-ASSEMBLY-REQUIRED")

                # Try to verify that it worked.
                self.pkg("list [email protected] [email protected]")
                self.pkg("verify -v")
                # make sure old implicit directories for bronzeA1 were removed
                self.assert_(not os.path.isdir(os.path.join(self.get_img_path(),
                    "A")))
                # Remove packages
                self.pkg("uninstall amber bronze")
                self.pkg("verify -v")

                # Make sure all directories are gone save /var in test image.
                self.assertEqual(set(os.listdir(self.get_img_path())),
                    set([".SELF-ASSEMBLY-REQUIRED", "var"]))

        def test_upgrade2(self):
                """ test incorporations:
                        1) install files that conflict w/ existing incorps
                        2) install package w/ dependencies that violate incorps
                        3) install incorp that violates existing incorp
                        4) install incorp that would force package backwards
                        """

                # Send all pkgs
                self.pkgsend_bulk(self.rurl, (self.incorp10, self.incorp20,
                    self.incorp30, self.iridium10, self.concorp10, self.amber10,
                    self.amber20, self.bronze10, self.bronze20, self.bronze30,
                    self.brass10))

                self.image_create(self.rurl)

                self.pkg("install [email protected]")
                # install files that conflict w/ existing incorps
                self.pkg("install [email protected]", exit=1)
                # install package w/ dependencies that violate incorps
                self.pkg("install [email protected]", exit=1)
                # install package w/ unspecified dependency that pulls
                # in bronze
                self.pkg("install brass")
                self.pkg("verify [email protected] [email protected]")
                # attempt to install conflicting incorporation
                self.pkg("install [email protected]", exit=1)

                # attempt to force downgrade of package w/ older incorp
                self.pkg("install [email protected]")
                self.pkg("uninstall [email protected]")
                self.pkg("install [email protected]")

                # upgrade pkg that loses incorp. deps. in new version
                self.pkg("install [email protected]")
                self.pkg("update")
                self.pkg("list [email protected]")

        def test_upgrade3(self):
                """Test for editable files moving between packages or locations
                or both."""

                install_cmd = "install"
                self.pkgsend_bulk(self.rurl, (self.silver10, self.silver20,
                    self.silver30, self.gold10, self.gold20, self.gold30,
                    self.golduser10, self.golduser20, self.silveruser))

                self.image_create(self.rurl)

                # test 1: move an editable file between packages
                self.pkg("%s --parsable=0 [email protected] [email protected]" % install_cmd)
                self._assertEditables(
                    installed=[
                        'etc/ftpd/ftpusers',
                        'etc/group',
                        'etc/passwd',
                        'etc/shadow',
                    ]
                )
                self.pkg("verify -v")

                # modify config file
                test_str = "this file has been modified 1"
                file_path = "etc/passwd"
                self.file_append(file_path, test_str)

                # make sure /etc/passwd contains correct string
                self.file_contains(file_path, test_str)

                # update packages
                self.pkg("%s -nvv [email protected] [email protected]" % install_cmd)
                self.pkg("%s --parsable=0 [email protected] [email protected]" % install_cmd)
                self._assertEditables()
                self.pkg("verify -v")

                # make sure /etc/passwd contains still correct string
                self.file_contains(file_path, test_str)

                self.pkg("uninstall --parsable=0 silver gold")
                self._assertEditables(
                    removed=[
                        'etc/ftpd/ftpusers',
                        'etc/group',
                        'etc/passwd',
                        'etc/shadow',
                    ],
                )


                # test 2: change an editable file's path within a package
                self.pkg("%s --parsable=0 [email protected]" % install_cmd)
                self.pkg("verify -v")

                # modify config file
                test_str = "this file has been modified test 2"
                file_path = "etc/passwd"
                self.file_append(file_path, test_str)

                self.pkg("%s --parsable=0 [email protected]" % install_cmd)
                self._assertEditables(
                    moved=[['etc/passwd', 'etc/config2']],
                    removed=[
                        'etc/ftpd/ftpusers',
                        'etc/group',
                        'etc/shadow',
                    ],
                )
                self.pkg("verify -v")

                # make sure /etc/config2 contains correct string
                file_path = "etc/config2"
                self.file_contains(file_path, test_str)

                self.pkg("uninstall --parsable=0 gold")
                self._assertEditables(
                    removed=['etc/config2'],
                )
                self.pkg("verify -v")


                # test 3: move an editable file between packages and change its path
                self.pkg("%s --parsable=0 [email protected] [email protected]" % install_cmd)
                self.pkg("verify -v")

                # modify config file
                file_path = "etc/passwd"
                test_str = "this file has been modified test 3"
                self.file_append(file_path, test_str)

                self.file_contains(file_path, test_str)

                self.pkg("%s --parsable=0 [email protected] [email protected]" % install_cmd)
                self._assertEditables(
                    moved=[['etc/passwd', 'etc/config2']],
                    removed=[
                        'etc/ftpd/ftpusers',
                        'etc/group',
                        'etc/shadow',
                    ],
                )
                self.pkg("verify -v")

                # make sure /etc/config2 now contains correct string
                file_path = "etc/config2"
                self.file_contains(file_path, test_str)

                self.pkg("uninstall --parsable=0 gold silver")


                # test 4: move /etc/passwd between packages and ensure that we
                # can still uninstall a user at the same time.
                self.pkg("%s --parsable=0 [email protected] [email protected]" % install_cmd)
                self.pkg("verify -v")

                # add a user
                self.pkg("install [email protected]")

                # make local changes to the user
                pwdpath = os.path.join(self.get_img_path(), "etc/passwd")

                pwdfile = file(pwdpath, "r+")
                lines = pwdfile.readlines()
                for i, l in enumerate(lines):
                        if l.startswith("Kermit"):
                                lines[i] = lines[i].replace("& User",
                                    "Kermit loves Miss Piggy")
                pwdfile.seek(0)
                pwdfile.writelines(lines)
                pwdfile.close()

                silly_path = os.path.join(self.get_img_path(), "etc/silly")
                silly_inode = os.stat(silly_path).st_ino

                # update packages
                self.pkg("%s --parsable=0 [email protected] [email protected] [email protected] "
                    "silveruser" % install_cmd)
                self._assertEditables()

                # make sure Kermie is still installed and still has our local
                # changes
                self.file_contains("etc/passwd",
                    "Kermit:x:5:4:Kermit loves Miss Piggy:/export/home/Kermit:")

                # also make sure that /etc/silly hasn't been removed and added
                # again, even though it wasn't marked specially
                self.assertEqual(os.stat(silly_path).st_ino, silly_inode)

        def test_upgrade4(self):
                """Test to make sure hardlinks are correctly restored when file
                they point to is updated."""

                self.pkgsend_bulk(self.rurl, (self.iron10, self.iron20))
                self.image_create(self.rurl)

                self.pkg("install [email protected]")
                self.pkg("verify -v")

                self.pkg("install [email protected]")
                self.pkg("verify -v")

        def test_upgrade5(self):
                """Test manually removed directory and files will be restored
                 during update, if mode are different."""

                self.pkgsend_bulk(self.rurl, (self.dumdir10, self.dumdir20))
                self.image_create(self.rurl)

                self.pkg("install -vvv [email protected]")
                self.pkg("verify -v")
                dirpath = os.path.join(self.test_root, "image0", "etc")
                shutil.rmtree(dirpath)

                self.pkg("update -vvv [email protected]")
                self.pkg("verify -v")

        def test_upgrade_liveroot(self):
                """Test to make sure upgrade of package fails if on live root
                and reboot is needed."""

                self.upgrade_liveroot_helper("install")
                self.upgrade_liveroot_helper("exact-install")

        def upgrade_liveroot_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, (self.liveroot10, self.liveroot20))
                self.image_create(self.rurl)

                self.pkg("--debug simulate_live_root=%s %s [email protected]" %
                    (self.get_img_path(), install_cmd))
                self.pkg("verify -v")
                self.pkg("--debug simulate_live_root=%s %s --deny-new-be "
                    "[email protected]" % (self.get_img_path(),  install_cmd),
                    exit=5)
                self.pkg("--debug simulate_live_root=%s uninstall "
                    "--deny-new-be liveroot" % self.get_img_path(), exit=5)
                # "break" liveroot@1
                self.file_append("etc/liveroot", "this file has been changed")
                self.pkg("--debug simulate_live_root=%s fix --deny-new-be "
                    "liveroot" % self.get_img_path(), exit=5)

        def test_upgrade_driver_conflicts(self):
                """Test to make sure driver_aliases conflicts don't cause
                add_drv to fail."""

                self.upgrade_driver_conflicts_helper("install")
                self.upgrade_driver_conflicts_helper("exact-install")

        def upgrade_driver_conflicts_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, (self.dricon1, self.dricon2,
                    self.dricon3))

                self.image_create(self.rurl)

                self.pkg("list -afv")
                self.pkg("%s dricon@1" % install_cmd)
                # This one should comment out the wigit entry in driver_aliases
                self.pkg("%s dricon@2" % install_cmd)
                da_contents = file(os.path.join(self.get_img_path(),
                    "etc/driver_aliases")).readlines()
                self.assert_("# pkg(5): wigit \"pci8086,1234\"\n" in da_contents)
                self.assert_("wigit \"pci8086,1234\"\n" not in da_contents)
                self.assert_("wigit \"pci8086,4321\"\n" in da_contents)
                self.assert_("zigit \"pci8086,1234\"\n" in da_contents)
                # This one should fail
                self.pkg("%s dricon@3" % install_cmd, exit=1)

        def test_driver_policy_removal(self):
                """Test for bug #9568 - that removing a policy for a
                driver where there is no minor node associated with it,
                works successfully.
                """

                self.driver_policy_removal_helper("install")
                self.driver_policy_removal_helper("exact-install")

        def driver_policy_removal_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, (self.dripol1, self.dripol2,
                    self.dripol3, self.dripol4, self.dripol5))

                self.image_create(self.rurl)

                self.pkg("list -afv")

                # Should install the frigit driver with a policy.
                self.pkg("%s dripol@1" % install_cmd)

                # Check that there is a policy entry for this
                # device in /etc/security/device_policy
                dp_contents = file(os.path.join(self.get_img_path(),
                    "etc/security/device_policy")).readlines()
                self.assert_("frigit:*\tread_priv_set=net_rawaccess\t"
                    "write_priv_set=net_rawaccess\ttpd_member=true\n"
                    in dp_contents)

                # Should reinstall the frigit driver without a policy.
                self.pkg("%s dripol@2" % install_cmd)

                # Check that there is no longer a policy entry for this
                # device in /etc/security/device_policy
                dp_contents = file(os.path.join(self.get_img_path(),
                    "etc/security/device_policy")).readlines()
                self.assert_("frigit:*\tread_priv_set=net_rawaccess\t"
                    "write_priv_set=net_rawaccess\ttpd_member=true\n"
                    not in dp_contents)

                self.pkg("update dripol@3")
                dp_contents = open(os.path.join(self.get_img_path(),
                    "etc/security/device_policy")).readlines()
                self.assert_("frigit:*\ttpd_member=true\n"
                    in dp_contents)

                self.pkg("update dripol@5")
                dp_contents = open(os.path.join(self.get_img_path(),
                    "etc/security/device_policy")).readlines()
                self.assert_("frigit:node1\tread_priv_set=all"
                    "\twrite_priv_set=all\ttpd_member=true\n"
                    in dp_contents)

                self.pkg("update dripol@4")
                dp_contents = open(os.path.join(self.get_img_path(),
                    "etc/security/device_policy")).readlines()
                self.assert_("frigit:node1" not in dp_contents)

        def test_file_preserve(self):
                """Verify that file preserve=true works as expected during
                package install, update, upgrade, and removal."""

                install_cmd = "install"
                self.pkgsend_bulk(self.rurl, (self.preserve1, self.preserve2,
                    self.preserve3, self.renpreserve))
                self.image_create(self.rurl)

                # If there are no local modifications, no preservation should be
                # done.  First with no content change ...
                self.pkg("%s --parsable=0 preserve@1" % install_cmd)
                self._assertEditables(
                    installed=['testme'],
                )
                self.pkg("%s --parsable=0 preserve@2" % install_cmd)
                self._assertEditables(
                    updated=['testme'],
                )
                self.file_contains("testme", "preserve1")
                self.pkg("verify preserve")

                self.pkg("update --parsable=0 preserve@1")
                self._assertEditables(
                    updated=['testme'],
                )
                self.file_contains("testme", "preserve1")
                self.pkg("verify preserve")

                self.pkg("uninstall --parsable=0 preserve")

                # ... and again with content change.
                self.pkg("install --parsable=0 preserve@1")
                self._assertEditables(
                    installed=['testme'],
                )
                self.pkg("install --parsable=0 preserve@3")
                self._assertEditables(
                    updated=['testme'],
                )
                self.file_contains("testme", "preserve3")

                self.pkg("update --parsable=0 preserve@1")
                self._assertEditables(
                    updated=['testme'],
                )

                self.file_contains("testme", "preserve1")

                self.pkg("verify preserve")
                self.pkg("uninstall --parsable=0 preserve")

                # Modify the file locally and update to a version where the
                # content changes.
                self.pkg("%s --parsable=0 preserve@1" % install_cmd)
                self.file_append("testme", "junk")
                self.file_contains("testme", "preserve1")
                self.pkg("%s --parsable=0 preserve@3" % install_cmd)
                self._assertEditables()
                self.file_contains("testme", "preserve1")
                self.file_contains("testme", "junk")
                self.file_doesnt_exist("testme.old")
                self.file_doesnt_exist("testme.new")
                self.pkg("verify preserve")
                self.pkg("uninstall --parsable=0 preserve")

                # Modify the file locally and downgrade to a version where
                # the content changes.
                self.pkg("%s --parsable=0 preserve@3" % install_cmd)
                self.file_append("testme", "junk")
                self.file_contains("testme", "preserve3")
                self.pkg("update --parsable=0 preserve@1")
                self._assertEditables(
                    moved=[['testme', 'testme.update']],
                    installed=['testme'],
                )
                self.file_doesnt_contain("testme", "preserve3")
                self.file_doesnt_contain("testme", "junk")
                self.file_doesnt_exist("testme.old")
                self.file_doesnt_exist("testme.new")
                self.file_exists("testme.update")
                self.file_remove("testme.update")
                self.pkg("verify preserve")
                self.pkg("uninstall --parsable=0 preserve")

                # Modify the file locally and update to a version where just the
                # mode changes.
                self.pkg("%s --parsable=0 preserve@1" % install_cmd)
                self.file_append("testme", "junk")

                self.pkg("%s --parsable=0 preserve@2" % install_cmd)
                self._assertEditables(
                    updated=['testme'],
                )
                self.file_contains("testme", "preserve1")
                self.file_contains("testme", "junk")
                self.file_doesnt_exist("testme.old")
                self.file_doesnt_exist("testme.new")

                self.pkg("update --parsable=0 preserve@1")
                self._assertEditables(
                    updated=['testme'],
                )
                self.file_contains("testme", "preserve1")
                self.file_contains("testme", "junk")
                self.file_doesnt_exist("testme.old")
                self.file_doesnt_exist("testme.new")
                self.file_doesnt_exist("testme.update")

                self.pkg("%s --parsable=0 preserve@2" % install_cmd)
                self._assertEditables(
                    updated=['testme'],
                )
                self.file_doesnt_exist("testme.old")
                self.file_doesnt_exist("testme.new")

                # Remove the file locally and update the package; this should
                # simply replace the missing file.
                self.file_remove("testme")
                self.pkg("%s --parsable=0 preserve@3" % install_cmd)
                self._assertEditables(
                    installed=['testme'],
                )
                self.pkg("verify preserve")
                self.file_exists("testme")

                # Remove the file locally and downgrade the package; this should
                # simply replace the missing file.
                self.file_remove("testme")
                self.pkg("update --parsable=0 preserve@2")
                self._assertEditables(
                    installed=['testme'],
                )
                self.pkg("verify preserve")
                self.file_exists("testme")
                self.pkg("uninstall preserve@2")

                # Verify preserved files will have their mode changed on update.
                self.pkg("%s --parsable=0 preserve@1" % install_cmd)
                self.pkg("%s --parsable=0 preserve@2" % install_cmd)
                self.pkg("verify preserve")

                # Verify that a package with a missing file that is marked with
                # the preserve=true won't cause uninstall failure.
                self.file_remove("testme")
                self.file_doesnt_exist("testme")
                self.pkg("uninstall --parsable=0 preserve")

                # Verify preserve works across package rename with and without
                # original_name use and even when the original file is missing.
                self.pkg("%s --parsable=0 [email protected]" % install_cmd)
                foo1_path = os.path.join(self.get_img_path(), "foo1")
                self.assert_(os.path.isfile(foo1_path))
                bronze1_path = os.path.join(self.get_img_path(), "bronze1")
                self.assert_(os.path.isfile(bronze1_path))

                # Update across the rename boundary, then verify that the files
                # were installed with their new name and the old ones were
                # removed.
                self.pkg("update -nvv orig_pkg")
                self.pkg("update --parsable=0 orig_pkg")
                self._assertEditables(
                    moved=[['foo1', 'foo2']],
                )

                foo2_path = os.path.join(self.get_img_path(), "foo2")
                self.assert_(not os.path.exists(foo1_path))
                self.assert_(os.path.isfile(foo2_path))
                self.assert_(os.path.isfile(bronze1_path))
                self.pkg("uninstall --parsable=0 \*")

                # Update across the rename boundary, then truncate each of the
                # preserved files.  They should remain empty even though one is
                # changing names and the other is simply being preserved across
                # a package rename.
                self.pkg("%s --parsable=0 [email protected]" % install_cmd)
                open(foo1_path, "wb").close()
                open(bronze1_path, "wb").close()
                self.pkg("update --parsable=0 orig_pkg")
                self._assertEditables(
                    moved=[['foo1', 'foo2']],
                )
                self.assert_(not os.path.exists(foo1_path))
                self.assert_(os.path.isfile(foo2_path))
                self.assertEqual(os.stat(foo2_path).st_size, 0)
                self.assert_(os.path.isfile(bronze1_path))
                self.assertEqual(os.stat(bronze1_path).st_size, 0)
                self.pkg("uninstall --parsable=0 \*")
                self._assertEditables(
                    removed=['bronze1', 'foo2'],
                )

                # Update across the rename boundary, then verify that a change
                # in file name will cause re-delivery of preserved files, but
                # unchanged, preserved files will not be re-delivered.
                self.pkg("%s --parsable=0 [email protected]" % install_cmd)
                os.unlink(foo1_path)
                os.unlink(bronze1_path)
                self.pkg("update --parsable=0 orig_pkg")
                self._assertEditables(
                    moved=[['foo1', 'foo2']],
                )
                self.assert_(not os.path.exists(foo1_path))
                self.assert_(os.path.isfile(foo2_path))
                self.assert_(not os.path.exists(bronze1_path))
                self.pkg("uninstall --parsable=0 \*")

                # Ensure directory is empty before testing.
                api_inst = self.get_img_api_obj()
                img_inst = api_inst.img
                sroot = os.path.join(img_inst.imgdir, "lost+found")
                shutil.rmtree(sroot)

                # Verify that unmodified, preserved files will not be salvaged
                # on uninstall.
                self.pkg("%s --parsable=0 [email protected]" % install_cmd)
                self.file_contains("testme", "preserve1")
                self.pkg("uninstall --parsable=0 preserve")
                salvaged = [
                    n for n in os.listdir(sroot)
                    if n.startswith("testme-")
                ]
                self.assertEqual(salvaged, [])

                # Verify that modified, preserved files will be salvaged
                # on uninstall.
                self.pkg("%s --parsable=0 [email protected]" % install_cmd)
                self.file_contains("testme", "preserve1")
                self.file_append("testme", "junk")
                self.pkg("uninstall --parsable=0 preserve")
                self.__salvage_file_contains(sroot, "testme", "junk")

        def test_file_preserve_renameold(self):
                """Make sure that file upgrade with preserve=renameold works."""

                install_cmd = "install"
                plist = self.pkgsend_bulk(self.rurl, (self.renameold1,
                    self.renameold2, self.renameold3))
                self.image_create(self.rurl)

                # If there are no local modifications, no preservation should be
                # done.  First with no content change ...
                self.pkg("%s renold@1" % install_cmd)
                self.pkg("%s renold@2" % install_cmd)
                self.file_contains("testme", "renold1")
                self.file_doesnt_exist("testme.old")
                self.file_doesnt_exist("testme.new")
                self.pkg("verify renold")
                self.pkg("uninstall renold")

                # ... and again with content change.
                self.pkg("%s renold@1" % install_cmd)
                self.pkg("%s renold@3" % install_cmd)
                self.file_contains("testme", "renold3")
                self.file_doesnt_exist("testme.old")
                self.file_doesnt_exist("testme.new")
                self.pkg("verify renold")
                self.pkg("uninstall renold")

                # Modify the file locally and update to a version where the
                # content changes.
                self.pkg("%s renold@1" % install_cmd)
                self.file_append("testme", "junk")
                self.pkg("%s --parsable=0 renold@3" % install_cmd)
                self._assertEditables(
                    moved=[['testme', 'testme.old']],
                    installed=['testme'],
                )
                self.file_contains("testme.old", "junk")
                self.file_doesnt_contain("testme", "junk")
                self.file_contains("testme", "renold3")
                self.dest_file_valid(plist, "[email protected]", "testme", "testme")
                self.file_doesnt_exist("testme.new")
                self.pkg("verify renold")
                self.pkg("uninstall renold")

                # Modify the file locally and update to a version where just the
                # mode changes.
                self.pkg("%s renold@1" % install_cmd)
                self.file_append("testme", "junk")
                self.pkg("%s --parsable=0 renold@2" % install_cmd)
                self._assertEditables(
                    moved=[['testme', 'testme.old']],
                    installed=['testme'],
                )
                self.file_contains("testme.old", "junk")
                self.file_doesnt_contain("testme", "junk")
                self.file_contains("testme", "renold1")
                self.file_doesnt_exist("testme.new")
                self.pkg("verify renold")
                self.pkg("uninstall renold")

                # Remove the file locally and update the package; this should
                # simply replace the missing file.
                self.pkg("%s renold@1" % install_cmd)
                self.file_remove("testme")
                self.pkg("%s --parsable=0 renold@2" % install_cmd)
                self._assertEditables(
                    installed=['testme'],
                )
                self.pkg("verify renold")
                self.pkg("uninstall renold")

        def test_file_preserve_renamenew(self):
                """Make sure that file ugprade with preserve=renamenew works."""

                install_cmd = "install"
                plist = self.pkgsend_bulk(self.rurl, (self.renamenew1,
                    self.renamenew2, self.renamenew3))
                self.image_create(self.rurl)

                # If there are no local modifications, no preservation should be
                # done.  First with no content change ...
                self.pkg("%s rennew@1" % install_cmd)
                self.pkg("%s --parsable=0 rennew@2" % install_cmd)
                self._assertEditables(
                    updated=['testme'],
                )
                self.file_contains("testme", "rennew1")
                self.file_doesnt_exist("testme.new")
                self.file_doesnt_exist("testme.old")
                self.pkg("verify rennew")
                self.pkg("uninstall rennew")

                # ... and again with content change
                self.pkg("%s rennew@1" % install_cmd)
                self.pkg("%s --parsable=0 rennew@3" % install_cmd)
                self._assertEditables(
                    updated=['testme'],
                )
                self.file_contains("testme", "rennew3")
                self.file_doesnt_exist("testme.new")
                self.file_doesnt_exist("testme.old")
                self.pkg("verify rennew")
                self.pkg("uninstall rennew")

                # Modify the file locally and update to a version where the
                # content changes.
                self.pkg("%s rennew@1" % install_cmd)
                self.file_append("testme", "junk")
                self.pkg("%s --parsable=0 rennew@3" % install_cmd)
                self._assertEditables(
                    installed=['testme.new'],
                )
                self.file_contains("testme", "junk")
                self.file_doesnt_contain("testme.new", "junk")
                self.file_contains("testme.new", "rennew3")
                self.dest_file_valid(plist, "[email protected]", "testme",
                    "testme.new")
                self.file_doesnt_exist("testme.old")
                self.pkg("verify rennew")
                self.pkg("uninstall rennew")

                # Modify the file locally and update to a version where just the
                # mode changes.
                self.pkg("%s rennew@1" % install_cmd)
                self.file_append("testme", "junk")
                self.pkg("%s --parsable=0 rennew@2" % install_cmd)
                self._assertEditables(
                    installed=['testme.new'],
                )
                self.file_contains("testme", "junk")
                self.file_doesnt_contain("testme.new", "junk")
                self.file_contains("testme.new", "rennew1")
                self.file_doesnt_exist("testme.old")

                # The original file won't be touched on update, so verify fails.
                self.pkg("verify rennew", exit=1)

                # Ensure that after fixing mode, verify passes.
                self.file_chmod("testme", 0640)
                self.pkg("verify rennew")
                self.pkg("uninstall rennew")
                self.file_remove("testme.new")

                # Remove the file locally and update the package; this should
                # simply replace the missing file.
                self.pkg("%s rennew@1" % install_cmd)
                self.file_remove("testme")
                self.pkg("%s --parsable=0 rennew@2" % install_cmd)
                self._assertEditables(
                    installed=['testme'],
                )
                self.file_doesnt_exist("testme.new")
                self.file_doesnt_exist("testme.old")
                self.pkg("verify rennew")
                self.pkg("uninstall rennew")

        def test_file_preserve_legacy(self):
                """Verify that preserve=legacy works as expected."""

                install_cmd = "install"
                self.pkgsend_bulk(self.rurl, (self.preslegacy,
                    self.renpreslegacy))
                self.image_create(self.rurl)

                # Ensure directory is empty before testing.
                api_inst = self.get_img_api_obj()
                img_inst = api_inst.img
                sroot = os.path.join(img_inst.imgdir, "lost+found")
                shutil.rmtree(sroot)

                # Verify that unpackaged files will be salvaged on initial
                # install if a package being installed delivers the same file
                # and that the new file will be installed.
                self.file_append("testme", "unpackaged")
                self.pkg("%s --parsable=0 [email protected]" % install_cmd)
                self._assertEditables(
                    installed=['testme'],
                )
                self.file_contains("testme", "preserve1")
                self.__salvage_file_contains(sroot, "testme", "unpackaged")
                shutil.rmtree(sroot)

                # Verify that a package transitioning to preserve=legacy from
                # some other state will have the existing file renamed using
                # .legacy as an extension.
                self.pkg("update --parsable=0 [email protected]")
                self._assertEditables(
                    moved=[['testme', 'testme.legacy']],
                    installed=['testme'],
                )
                self.file_contains("testme.legacy", "preserve1")
                self.file_contains("testme", "preserve2")

                # Verify that if an action with preserve=legacy is upgraded
                # and its payload changes that the new payload is delivered
                # but the old .legacy file is not modified.
                self.pkg("update --parsable=0 [email protected]")
                self._assertEditables(
                    updated=['testme'],
                )
                self.file_contains("testme.legacy", "preserve1")
                self.file_contains("testme", "preserve3")

                # Verify that if the file for an action marked with
                # preserve=legacy is removed that the package still
                # verifies.
                self.file_remove("testme")
                self.pkg("verify -v preslegacy")

                # Verify that a file removed for an action marked with
                # preserve=legacy can be reverted.
                self.pkg("revert testme")
                self.file_contains("testme", "preserve3")

                # Verify that an initial install of an action with
                # preserve=legacy will not install the payload of the action.
                self.pkg("uninstall preslegacy")
                self.pkg("%s --parsable=0 [email protected]" % install_cmd)
                self._assertEditables()
                self.file_doesnt_exist("testme")

                # Verify that if the original preserved file is missing during
                # a transition to preserve=legacy from some other state that
                # the new action is still delivered and the operation succeeds.
                self.pkg("uninstall preslegacy")
                self.pkg("%s --parsable=0 [email protected]" % install_cmd)
                self._assertEditables(
                    installed=['testme'],
                )
                self.file_remove("testme")
                self.pkg("update --parsable=0")
                self._assertEditables(
                    installed=['testme'],
                )
                self.file_contains("testme", "preserve3")

                # Verify that a preserved file can be moved from one package to
                # another and transition to preserve=legacy at the same time.
                self.pkg("uninstall preslegacy")
                self.pkg("%s --parsable=0 [email protected]" % install_cmd)
                self._assertEditables(
                    installed=['testme'],
                )
                self.file_exists("testme")
                self.pkg("update --parsable=0")
                self._assertEditables(
                    moved=[['testme', 'newme.legacy']],
                    installed=['newme'],
                )
                self.file_contains("testme.legacy", "preserve1")
                self.file_contains("newme", "preserve2")

        def test_file_preserve_abandon(self):
                """Verify that preserve=abandon works as expected."""

                install_cmd = "install"
                self.pkgsend_bulk(self.rurl, self.presabandon)
                self.image_create(self.rurl)

                # Ensure directory is empty before testing.
                api_inst = self.get_img_api_obj()
                img_inst = api_inst.img
                sroot = os.path.join(img_inst.imgdir, "lost+found")
                shutil.rmtree(sroot)

                # Verify that unpackaged files will not be salvaged on initial
                # install if a package being installed delivers the same file
                # and that the new file will not be installed.
                self.file_append("testme", "unpackaged")
                self.pkg("%s --parsable=0 presabandon@2" % install_cmd)
                self._assertEditables()
                self.file_contains("testme", "unpackaged")
                self.assert_(not os.path.exists(os.path.join(sroot, "testme")))
                self.file_remove("testme")
                self.pkg("uninstall presabandon")

                # Verify that an initial install of an action with
                # preserve=abandon will not install the payload of the action.
                self.pkg("%s --parsable=0 presabandon@2" % install_cmd)
                self._assertEditables()
                self.file_doesnt_exist("testme")
                self.pkg("uninstall presabandon")

                # If an action delivered by the upgraded version of the package
                # has a preserve=abandon, the new file will not be installed and
                # the existing file will not be modified.

                # First with no content change ...
                self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
                self._assertEditables(
                    installed=['testme'],
                )
                self.pkg("update --parsable=0 presabandon@2")
                self._assertEditables()
                self.file_contains("testme", "preserve1")
                # The currently installed version of the package has a preserve
                # value of abandon, so the file will not be removed.
                self.pkg("uninstall --parsable=0 presabandon")
                self._assertEditables()
                self.file_exists("testme")

                # If an action delivered by the downgraded version of the package
                # has a preserve=abandon, the new file will not be installed and
                # the existing file will not be modified.
                self.pkg("%s --parsable=0 presabandon@4" % install_cmd)
                self._assertEditables(
                    installed=['testme'],
                )
                self.file_contains("testme", "preserve3")
                self.pkg("verify presabandon")
                self.pkg("update --parsable=0 presabandon@3")
                self._assertEditables()
                self.file_contains("testme", "preserve3")
                self.pkg("verify presabandon")
                self.pkg("uninstall --parsable=0 presabandon")
                self.file_remove("testme")

                # ... and again with content change.
                self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
                self.pkg("%s --parsable=0 presabandon@3" % install_cmd)
                self._assertEditables()
                self.file_contains("testme", "preserve1")
                self.pkg("uninstall --parsable=0 presabandon")

                self.pkg("install --parsable=0 presabandon@4")
                self._assertEditables(
                    installed=['testme'],
                )
                self.file_contains("testme", "preserve3")
                self.pkg("update --parsable=0 presabandon@2")
                self._assertEditables()
                self.file_contains("testme", "preserve3")
                self.pkg("verify presabandon")
                self.pkg("uninstall --parsable=0 presabandon")
                self.file_remove("testme")

                # Modify the file locally and upgrade to a version where the
                # file has a preserve=abandon attribute and the content changes.
                self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
                self.file_append("testme", "junk")
                self.pkg("%s --parsable=0 presabandon@3" % install_cmd)
                self._assertEditables()
                self.file_contains("testme", "preserve1")
                self.file_contains("testme", "junk")
                self.file_doesnt_exist("testme.old")
                self.file_doesnt_exist("testme.new")
                self.pkg("uninstall --parsable=0 presabandon")
                self.file_remove("testme")

                # Modify the file locally and downgrade to a version where the
                # file has a preserve=abandon attribute and the content changes.
                self.pkg("%s --parsable=0 presabandon@4" % install_cmd)
                self.file_append("testme", "junk")
                self.file_contains("testme", "preserve3")
                self.pkg("update --parsable=0 presabandon@2")
                self._assertEditables()
                self.file_contains("testme", "preserve3")
                self.file_contains("testme", "junk")
                self.file_doesnt_exist("testme.old")
                self.file_doesnt_exist("testme.new")
                self.file_doesnt_exist("testme.update")
                self.pkg("verify presabandon")
                self.pkg("uninstall --parsable=0 presabandon")
                self.file_remove("testme")

                # Modify the file locally and upgrade to a version where the
                # file has a preserve=abandon attribute and just the mode changes.
                self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
                self.file_append("testme", "junk")
                self.pkg("%s --parsable=0 presabandon@2" % install_cmd)
                self._assertEditables()
                self.file_contains("testme", "preserve1")
                self.file_contains("testme", "junk")
                self.file_doesnt_exist("testme.old")
                self.file_doesnt_exist("testme.new")
                self.pkg("verify presabandon")
                self.pkg("uninstall --parsable=0 presabandon")
                self.file_remove("testme")

                # Modify the file locally and downgrade to a version where the
                # file has a preserve=abandon attribute and just the mode changes.
                self.pkg("%s --parsable=0 presabandon@4" % install_cmd)
                self.file_append("testme", "junk")
                self.pkg("update --parsable=0 presabandon@3")
                self._assertEditables()
                self.file_contains("testme", "preserve3")
                self.file_contains("testme", "junk")
                self.file_doesnt_exist("testme.old")
                self.file_doesnt_exist("testme.new")
                self.file_doesnt_exist("testme.update")
                self.pkg("verify presabandon")
                self.pkg("uninstall --parsable=0 presabandon")
                self.file_remove("testme")

                # Remove the file locally and update the package where the
                # file has a preserve=abandon attribute; this will not replace
                # the missing file.
                self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
                self.file_remove("testme")
                self.pkg("%s --parsable=0 presabandon@2" % install_cmd)
                self._assertEditables()
                self.file_doesnt_exist("testme")
                self.pkg("uninstall --parsable=0 presabandon")

                # Remove the file locally and downgrade the package where the
                # file has a preserve=abandon attribute; this will not replace
                # the missing file.
                self.pkg("%s --parsable=0 presabandon@4" % install_cmd)
                self.file_remove("testme")
                self.pkg("update --parsable=0 presabandon@3")
                self._assertEditables()

                # Verify that a package with a missing file that is marked with
                # the preserve=abandon won't cause uninstall failure.
                self.file_doesnt_exist("testme")
                self.pkg("uninstall --parsable=0 presabandon")

                # Verify that if the file for an action marked with
                # preserve=abandon is removed that the package still
                # verifies.
                self.pkg("%s --parsable=0 presabandon@1" % install_cmd)
                self.pkg("%s --parsable=0 presabandon@2" % install_cmd)
                self.file_remove("testme")
                self.pkg("verify -v presabandon")

                # Verify that a file removed for an action marked with
                # preserve=abandon can be reverted.
                self.pkg("revert testme")
                self.file_contains("testme", "preserve1")

        def test_directory_salvage(self):
                """Make sure basic directory salvage works as expected"""

                self.pkgsend_bulk(self.rurl, self.salvage)
                self.image_create(self.rurl)
                self.pkg("install [email protected]")
                self.file_append("var/mail/foo", "foo's mail")
                self.file_append("var/mail/bar", "bar's mail")
                self.file_append("var/mail/baz", "baz's mail")
                self.pkg("update salvage")
                self.file_exists("var/.migrate-to-shared/mail/foo")
                self.file_exists("var/.migrate-to-shared/mail/bar")
                self.file_exists("var/.migrate-to-shared/mail/baz")

        def test_directory_salvage_persistent(self):
                """Make sure directory salvage works as expected when salvaging
                content to an existing packaged directory."""

                # we salvage content from two directories,
                # var/noodles and var/spaghetti each of which disappear over
                # subsequent updates.
                self.pkgsend_bulk(self.rurl, self.salvage)
                self.image_create(self.rurl)
                self.pkg("install [email protected]")
                self.file_append("var/mail/foo", "foo's mail")
                self.file_append("var/noodles/noodles.txt", "yum")
                self.pkg("update [email protected]")
                self.file_exists("var/.migrate-to-shared/mail/foo")
                self.file_exists("var/persistent/noodles.txt")
                self.file_append("var/spaghetti/spaghetti.txt", "yum")
                self.pkg("update")
                self.file_exists("var/persistent/noodles.txt")
                self.file_exists("var/persistent/spaghetti.txt")

                # ensure that we can jump from 1.0 to 3.0 directly.
                self.image_create(self.rurl)
                self.pkg("install [email protected]")
                self.file_append("var/noodles/noodles.txt", "yum")
                self.pkg("update  [email protected]")
                self.file_exists("var/persistent/noodles.txt")

        def test_special_salvage(self):
                """Make sure salvaging directories with special files works as
                expected."""

                self.pkgsend_bulk(self.rurl, self.salvage_special)
                self.image_create(self.rurl, destroy=True, fs=("var",))

                self.pkg("install salvage-special")

                os.mkfifo(os.path.join(self.img_path(), "salvage", "fifo"))
                sock = socket.socket(socket.AF_UNIX)
                sock.bind(os.path.join(self.img_path(), "salvage", "socket"))
                sock.close()
                # We also test block and character special files, but only if
                # os.mknod() is available, which it isn't always.
                if hasattr(os, "mknod"):
                        st = os.stat("/dev/null")
                        os.mknod(os.path.join(self.img_path(), "salvage",
                            "node"), st.st_mode, st.st_dev)

                # This could hang reading fifo, or keel over reading socket.
                self.pkg("uninstall salvage-special")

        def dest_file_valid(self, plist, pkg, src, dest):
                """Used to verify that the dest item's mode, attrs, timestamp,
                etc. match the src items's matching action as expected."""

                for p in plist:
                        pfmri = fmri.PkgFmri(p, "5.11")
                        pfmri.publisher = None
                        sfmri = pfmri.get_short_fmri().replace("pkg:/", "")

                        if pkg != sfmri:
                                continue

                        m = manifest.Manifest()
                        m.set_content(self.get_img_manifest(pfmri))
                        for a in m.gen_actions():
                                if a.name != "file":
                                        # Only want file actions that have
                                        # preserve attribute.
                                        continue
                                if a.attrs["path"] != src:
                                        # Only want actions with matching path.
                                        continue
                                self.validate_fsobj_attrs(a, target=dest)

        def test_link_preserve(self):
                """Ensure that files transitioning to a link still follow
                original_name preservation rules."""

                self.pkgsend_bulk(self.rurl, (self.linkpreserve))
                self.image_create(self.rurl, destroy=True, fs=("var",))

                # Install package with original config file location.
                self.pkg("install --parsable=0 [email protected]")
                cfg_path = os.path.join("etc", "ssh", "sshd_config")
                abs_path = os.path.join(self.get_img_path(), cfg_path)

                self.file_exists(cfg_path)
                self.assert_(not os.path.islink(abs_path))

                # Modify the file.
                self.file_append(cfg_path, "modified")

                # Install new package version, verify file replaced with link
                # and modified version was moved to new location.
                new_cfg_path = os.path.join("etc", "sunssh", "sshd_config")
                self.pkg("update --parsable=0 [email protected]")
                self._assertEditables(
                    moved=[['etc/ssh/sshd_config', 'etc/sunssh/sshd_config']]
                )
                self.assert_(os.path.islink(abs_path))
                self.file_exists(new_cfg_path)
                self.file_contains(new_cfg_path, "modified")

                # Uninstall, then install original version again.
                self.pkg("uninstall linkpreserve")
                self.pkg("install [email protected]")
                self.file_contains(cfg_path, "preserve1")

                # Install new package version and verify that unmodified file is
                # replaced with new configuration file.
                self.pkg("update --parsable=0 [email protected]")
                self._assertEditables(
                    moved=[['etc/ssh/sshd_config', 'etc/sunssh/sshd_config']]
                )
                self.file_contains(new_cfg_path, "preserve2")

        def test_many_hashalgs(self):
                """Test that when upgrading actions where the new action
                contains more hash attributes than the old action, that the
                upgrade works."""

                self.many_hashalgs_helper("install", "sha256")
                self.many_hashalgs_helper("exact-install", "sha256")
                if sha512_supported:
                        self.many_hashalgs_helper("install", "sha512_256")
                        self.many_hashalgs_helper("exact-install", "sha512_256")

        def many_hashalgs_helper(self, install_cmd, hash_alg):
                self.pkgsend_bulk(self.rurl, (self.iron10))
                self.image_create(self.rurl, destroy=True)
                self.pkg("install [email protected]")
                self.pkg("contents -m iron")
                # We have not enabled SHA2 hash publication yet.
                self.assert_(("pkg.hash.%s" % hash_alg) not in self.output)

                # publish with SHA1 and SHA2 hashes
                self.pkgsend_bulk(self.rurl, self.iron20,
                    debug_hash="sha1+%s" % hash_alg)

                # verify that a non-SHA2 aware client can install these bits
                self.pkg("-D hash=sha1 update")
                self.image_create(self.rurl, destroy=True)

                # This also tests package retrieval: we always retrieve packages
                # with the least-preferred hash, but verify with the
                # most-preferred hash.
                self.pkg("install [email protected]")
                self.pkg("contents -m iron")
                self.assert_("pkg.hash.%s" % hash_alg in self.output)

                # publish with only SHA-2 hashes
                self.pkgsend_bulk(self.rurl, self.iron20,
                    debug_hash="%s" % hash_alg)

                # verify that a non-SHA2 aware client cannot install these bits
                # since there are no SHA1 hashes present
                self.pkg("-D hash=sha1 update", exit=1)
                self.assert_(
                    "No file could be found for the specified hash name: "
                    "'NOHASH'" in self.errout)

                # Make sure we've been publishing only with SHA2 by removing
                # those known attributes, then checking for the presence of
                # the SHA-1 attributes.
                self.pkg("-D hash=%s update" % hash_alg)
                self.pkg("contents -m iron")
                for attr in ["pkg.hash.%s" % hash_alg,
                    "pkg.chash.%s" % hash_alg]:
                        self.output = self.output.replace(attr, "")
                self.assert_("hash" not in self.output)
                self.assert_("chash" not in self.output)

        def test_content_hash_ignore(self):
                """Test that pkgs with content-hash attributes are ignored for
                install and verify by default."""

                elfpkg_1 = """
                    open [email protected]
                    add file %s mode=0755 owner=root group=bin path=/bin/true
                    close """
                elfpkg = elfpkg_1 % os.path.join("ro_data", "elftest.so.1")
                elf1 = self.pkgsend_bulk(self.rurl, (elfpkg,))[0]

                repo_dir = self.dcs[1].get_repodir()
                f = fmri.PkgFmri(elf1, None)
                repo = self.get_repo(repo_dir)
                mpath = repo.manifest(f)
                # load manifest, add content-hash attr and store back to disk
                mani = manifest.Manifest()
                mani.set_content(pathname=mpath)
                for a in mani.gen_actions():
                        if "bin/true" in str(a):
                                a.attrs["pkg.content-hash.sha256"] = "foo"
                mani.store(mpath)
                # rebuild repo catalog since manifest digest changed
                repo.rebuild()

                # assert that the current pkg gate has the correct hash ranking
                self.assertTrue(len(digest.RANKED_CONTENT_HASH_ATTRS) > 0)
                self.assertEqual(digest.RANKED_CONTENT_HASH_ATTRS[0], "elfhash")

                # test that pkgrecv, pkgrepo verify, pkg install and pkg verify
                # do not complain about unknown hash
                self.pkgrecv("%s -a -d %s '*'" % (repo_dir,
                    os.path.join(self.test_root, "x.p5p")))
                self.pkgrepo("verify -s %s" % repo_dir)
                self.image_create(self.rurl, destroy=True)
                self.pkg("install -v %s" % elf1)
                # Note that we pass verification if any of the hashes match, but
                # we require by default that the content hash matches.
                self.pkg("verify")


class TestPkgInstallActions(pkg5unittest.SingleDepotTestCase):
        # Only start/stop the depot once (instead of for every test)
        persistent_setup = True

        misc_files = {
                "ftpusers" :
"""# ident      "@(#)ftpusers   1.6     06/11/21 SMI"
#
# List of users denied access to the FTP server, see ftpusers(4).
#
root
bin
sys
adm
""",
                "group" :
"""root::0:
other::1:root
bin::2:root,daemon
sys::3:root,bin,adm
adm::4:root,daemon
+::::
""",
                "passwd" :
"""root:x:0:0::/root:/usr/bin/bash
daemon:x:1:1::/:
bin:x:2:2::/usr/bin:
sys:x:3:3::/:
adm:x:4:4:Admin:/var/adm:
+::::::
""",
                "shadow" :
"""root:9EIfTNBp9elws:13817::::::
daemon:NP:6445::::::
bin:NP:6445::::::
sys:NP:6445::::::
adm:NP:6445::::::
+::::::::
""",
                "cat" : " ",
                "empty" : ""
        }


        foo10 = """
            open [email protected],5.11-0
            close """

        only_attr10 = """
            open [email protected],5.11-0
            add set name=foo value=bar
            close """

        only_depend10 = """
            open [email protected],5.11-0
            add depend type=require [email protected],5.11-0
            close """

        only_directory10 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=/bin
            close """

        only_driver10 = """
            open [email protected],5.11-0
            add driver name=zerg devlink="type=ddi_pseudo;name=zerg\\t\D"
            close """

        only_group10 = """
            open [email protected],5.11-0
            add group groupname=Kermit gid=28
            close """

        only_group_file10 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=Kermit path=/export/home/Kermit
            close """

        only_hardlink10 = """
            open [email protected],5.11-0
            add hardlink path=/cat.hardlink target=/cat
            close """

        only_legacy10 = """
            open [email protected],5.11-0
            add legacy category=system desc="GNU make - A utility used to build software (gmake) 3.81" hotline="Please contact your local service provider" name="gmake - GNU make" pkg=SUNWgmake vendor="Sun Microsystems, Inc." version=11.11.0,REV=2008.04.29.02.08
            close """

        only_link10 = """
            open [email protected],5.11-0
            add link path=/link target=/tmp/cat
            close """

        only_user10 = """
            open [email protected],5.11-0
            add user username=Kermit group=adm home-dir=/export/home/Kermit
            close """

        only_user_file10 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=Kermit group=adm path=/export/home/Kermit
            close """

        csu1 = """
            open [email protected],5.11-0
            add legacy category=system desc="core software for a specific instruction-set architecture" hotline="Please contact your local service provider" name="Core Solaris, (Usr)" pkg=SUNWcsu vendor="Oracle Corporation" version=11.11,REV=2009.11.11
            close
        """

        csu1_2 = """
            open [email protected],5.11-0
            add legacy category=system desc="core software for a specific instruction-set architecture" hotline="Please contact your local service provider" name="Core Solaris, (Usr)" pkg=SUNWcsu vendor="Oracle Corporation" version=11.11,REV=2010.11.11
            close
        """

        csu2 = """
            open [email protected],5.11-0
            add legacy category=system desc="core software for a specific instruction-set architecture" hotline="Please contact your local service provider" name="Core Solaris, (Usr)" pkg=SUNWcsu vendor="Oracle Corporation" version=11.11,REV=2009.11.11
            close
        """

        csu2_2 = """
            open [email protected],5.11-0
            add legacy category=system desc="core software for a specific instruction-set architecture" hotline="Please contact your local service provider" name="Core Solaris, (Usr)" pkg=SUNWcsu vendor="Oracle Corporation" version=11.11,REV=2010.11.11
            close
        """

        csu3 = """
            open [email protected],5.11-0
            add legacy category=system desc="core software for a specific instruction-set architecture" hotline="Please contact your local service provider" name="Core Solaris, (Usr)" pkg=SUNWcsu vendor="Oracle Corporation" version=11.11,REV=2009.11.11
            close
        """

        csu3_2 = """
            open [email protected],5.11-0
            add legacy category=system desc="core software for a specific instruction-set architecture" hotline="Please contact your local service provider" name="Core Solaris, (Usr)" pkg=SUNWcsu vendor="Oracle Corporation" version=11.11,REV=2010.11.11
            close
        """

        # some of these are subsets-- "always" and "at-end"-- for performance;
        # we assume that e.g. if a and z work, that bcdef, etc. will too.
        pkg_name_valid_chars = {
            "never": " `~!@#$%^&*()=[{]}\\|;:\",<>?",
            "always": "09azAZ",
            "after-first": "_-.+",
            "at-end": "09azAZ_-.+",
        }

        def setUp(self):

                pkg5unittest.SingleDepotTestCase.setUp(self)

                self.only_file10 = """
                    open [email protected],5.11-0
                    add file cat mode=0555 owner=root group=bin path=/cat
                    close """

                self.only_license10 = """
                    open [email protected],5.11-0
                    add license cat license=copyright
                    close """

                self.baseuser = """
                    open system/action/user@0,5.11
                    add dir path=etc mode=0755 owner=root group=sys
                    add dir path=etc/ftpd mode=0755 owner=root group=sys
                    add user username=root password=9EIfTNBp9elws uid=0 group=root home-dir=/root gcos-field=Super-User login-shell=/usr/bin/bash ftpuser=false lastchg=13817 group-list=other group-list=bin group-list=sys group-list=adm
                    add group gid=0 groupname=root
                    add group gid=3 groupname=sys
                    add file empty path=etc/group mode=0644 owner=root group=sys preserve=true
                    add file empty path=etc/passwd mode=0644 owner=root group=sys preserve=true
                    add file empty path=etc/shadow mode=0400 owner=root group=sys preserve=true
                    add file empty path=etc/ftpd/ftpusers mode=0644 owner=root group=sys preserve=true
                    add file empty path=etc/user_attr mode=0644 owner=root group=sys preserve=true
                    close """

                self.singleuser = """
                    open singleuser@0,5.11
                    add user group=fozzie uid=16 username=fozzie
                    add group groupname=fozzie gid=16
                    close
                """

                self.basics0 = """
                    open [email protected],5.11-0
                    add file passwd mode=0644 owner=root group=sys path=etc/passwd preserve=true
                    add file shadow mode=0400 owner=root group=sys path=etc/shadow preserve=true
                    add file group mode=0644 owner=root group=sys path=etc/group preserve=true
                    add file ftpusers mode=0644 owner=root group=sys path=etc/ftpd/ftpusers preserve=true
                    add dir mode=0755 owner=root group=sys path=etc
                    add dir mode=0755 owner=root group=sys path=etc/ftpd
                    close """

                self.basics1 = """
                    open [email protected],5.11-0
                    add dir mode=0755 owner=root group=bin path=lib
                    add dir mode=0755 owner=root group=sys path=var
                    add dir mode=0755 owner=root group=sys path=var/svc
                    add dir mode=0755 owner=root group=sys path=var/svc/manifest
                    add dir mode=0755 owner=root group=bin path=usr
                    add dir mode=0755 owner=root group=bin path=usr/local
                    close """

                self.grouptest = """
                    open [email protected],5.11-0
                    add dir mode=0755 owner=root group=Kermit path=/usr/Kermit
                    add file empty mode=0755 owner=root group=Kermit path=/usr/local/bin/do_group_nothing
                    add group groupname=lp gid=8
                    add group groupname=staff gid=10
                    add group groupname=Kermit
                    add depend fmri=pkg:/[email protected] type=require
                    close """

                self.usertest10 = """
                    open [email protected],5.11-0
                    add dir mode=0755 owner=Kermit group=Kermit path=/export/home/Kermit
                    add file empty mode=0755 owner=Kermit group=Kermit path=/usr/local/bin/do_user_nothing
                    add depend fmri=pkg:/[email protected] type=require
                    add user username=Kermit group=Kermit home-dir=/export/home/Kermit group-list=lp group-list=staff
                    add depend fmri=pkg:/[email protected] type=require
                    add depend fmri=pkg:/[email protected] type=require
                    close """

                self.usertest11 = """
                    open [email protected],5.11-0
                    add dir mode=0755 owner=Kermit group=Kermit path=/export/home/Kermit
                    add file empty mode=0755 owner=Kermit group=Kermit path=/usr/local/bin/do_user_nothing
                    add depend fmri=pkg:/[email protected] type=require
                    add user username=Kermit group=Kermit home-dir=/export/home/Kermit2 group-list=lp group-list=staff group-list=root ftpuser=false
                    add depend fmri=pkg:/[email protected] type=require
                    add depend fmri=pkg:/[email protected] type=require
                    close """

                self.ugidtest = """
                    open [email protected],5.11-0
                    add user username=dummy group=root
                    add group groupname=dummy
                    close """

                self.silver10 = """
                    open [email protected],5.11-0
                    add file empty mode=0755 owner=root group=root path=/usr/local/bin/silver
                    add depend fmri=pkg:/[email protected] type=require
                    add depend fmri=pkg:/[email protected] type=require
                    close """
                self.silver20 = """
                    open [email protected],5.11-0
                    add file empty mode=0755 owner=Kermit group=Kermit path=/usr/local/bin/silver
                    add user username=Kermit group=Kermit home-dir=/export/home/Kermit group-list=lp group-list=staff
                    add depend fmri=pkg:/[email protected] type=require
                    add depend fmri=pkg:/[email protected] type=require
                    add depend fmri=pkg:/[email protected] type=require
                    close """

                self.devicebase = """
                    open [email protected],5.11-0
                    add dir mode=0755 owner=root group=sys path=/var
                    add dir mode=0755 owner=root group=sys path=/var/run
                    add dir mode=0755 owner=root group=root path=system
                    add dir mode=0755 owner=root group=root path=system/volatile
                    add dir mode=0755 owner=root group=sys path=/tmp
                    add dir mode=0755 owner=root group=sys path=/etc
                    add dir mode=0755 owner=root group=sys path=/etc/security
                    add file empty mode=0600 owner=root group=sys path=/etc/devlink.tab preserve=true
                    add file empty mode=0644 owner=root group=sys path=/etc/name_to_major preserve=true
                    add file empty mode=0644 owner=root group=sys path=/etc/driver_aliases preserve=true
                    add file empty mode=0644 owner=root group=sys path=/etc/driver_classes preserve=true
                    add file empty mode=0644 owner=root group=sys path=/etc/minor_perm preserve=true
                    add file empty mode=0644 owner=root group=sys path=/etc/security/device_policy preserve=true
                    add file empty mode=0644 owner=root group=sys path=/etc/security/extra_privs preserve=true
                    close
                """

                self.devlink10 = """
                    open [email protected],5.11-0
                    add driver name=zerg devlink="type=ddi_pseudo;name=zerg\\t\D"
                    add driver name=borg devlink="type=ddi_pseudo;name=borg\\t\D" devlink="type=ddi_pseudo;name=warg\\t\D"
                    add depend type=require fmri=devicebase
                    close
                """

                self.devlink20 = """
                    open [email protected],5.11-0
                    add driver name=zerg devlink="type=ddi_pseudo;name=zerg2\\t\D" devlink="type=ddi_pseudo;name=zorg\\t\D"
                    add driver name=borg devlink="type=ddi_pseudo;name=borg\\t\D" devlink="type=ddi_pseudo;name=zork\\t\D"
                    add depend type=require fmri=devicebase
                    close
                """

                self.devalias10 = """
                    open devalias@1,5.11
                    add driver name=zerg alias=pci8086,1234 alias=pci8086,4321
                    close
                """

                self.devalias20 = """
                    open devalias@2,5.11
                    add driver name=zerg alias=pci8086,5555
                    close
                """

                self.devaliasmove10 = """
                    open devaliasmove@1,5.11
                    add driver name=zerg alias=pci8086,5555
                    close
                """

                self.devaliasmove20 = """
                    open devaliasmove@2,5.11
                    add driver name=zerg
                    add driver name=borg alias=pci8086,5555
                    close
                """

                self.badhardlink1 = """
                    open [email protected],5.11-0
                    add hardlink path=foo target=bar
                    close
                """

                self.badhardlink2 = """
                    open [email protected],5.11-0
                    add file cat mode=0555 owner=root group=bin path=/etc/motd
                    add hardlink path=foo target=/etc/motd
                    close
                """

                self.make_misc_files(self.misc_files)

        def test_basics_0_install(self):
                """Send basic infrastructure, install and uninstall."""

                self.pkgsend_bulk(self.rurl, (self.basics0, self.basics1))
                self.image_create(self.rurl)

                self.pkg("list -a")
                self.pkg("list", exit=1)

                self.pkg("install basics")
                self.pkg("install basics1")

                self.pkg("list")
                self.pkg("verify")

                self.pkg("uninstall basics basics1")
                self.pkg("verify")

        def test_basics_0_exact_install(self):
                """Send basic infrastructure, exact-install and uninstall."""

                self.pkgsend_bulk(self.rurl, (self.basics0, self.basics1))
                self.image_create(self.rurl)

                self.pkg("list -a")
                self.pkg("list", exit=1)

                self.pkg("exact-install basics basics1")

                self.pkg("list")
                self.pkg("verify")

                self.pkg("uninstall basics basics1")
                self.pkg("verify")

        def test_grouptest_install(self):

                self.pkgsend_bulk(self.rurl, (self.basics0, self.basics1,
                    self.grouptest))
                self.image_create(self.rurl)
                self.pkg("install basics")
                self.pkg("install basics1")

                self.pkg("install grouptest")
                self.pkg("verify")
                self.pkg("uninstall grouptest")
                self.pkg("verify")

        def test_grouptest_exact_install(self):

                self.pkgsend_bulk(self.rurl, (self.basics0, self.basics1,
                    self.grouptest))
                self.image_create(self.rurl)
                self.pkg("exact-install basics basics1")
                self.pkg("exact-install grouptest")
                self.pkg("verify")
                self.pkg("list basics1", exit=1)
                self.pkg("uninstall grouptest")
                self.pkg("verify")

        def test_usertest_install(self):

                self.pkgsend_bulk(self.rurl, (self.basics0, self.basics1,
                    self.grouptest, self.usertest10))
                self.image_create(self.rurl)
                self.pkg("install basics")
                self.pkg("install basics1")

                self.pkg("install usertest")
                self.pkg("verify")
                self.pkg("contents -m usertest")

                self.pkgsend_bulk(self.rurl, self.usertest11)
                self.pkg("refresh")
                self.pkg("install usertest")
                self.pkg("verify")
                self.pkg("contents -m usertest")

                self.pkg("uninstall usertest")
                self.pkg("verify")

        def test_usertest_exact_install(self):

                self.pkgsend_bulk(self.rurl, (self.basics0, self.basics1,
                    self.grouptest, self.usertest10))
                self.image_create(self.rurl)
                self.pkg("exact-install basics basics1")
                self.pkg("exact-install usertest")
                self.pkg("verify")
                self.pkg("list basics1", exit=1)
                self.pkg("contents -m usertest")

                self.pkgsend_bulk(self.rurl, self.usertest11)
                self.pkg("refresh")
                self.pkg("exact-install usertest")
                self.pkg("verify")
                self.pkg("contents -m usertest")

                self.pkg("uninstall usertest")
                self.pkg("verify")

        def test_primordial_usergroup_install(self):
                """Ensure that we can install user and group actions in the same
                transaction as /etc/passwd, /etc/group, etc."""

                self.pkgsend_bulk(self.rurl, [self.baseuser, self.singleuser])

                self.image_create(self.rurl)
                self.pkg("install system/action/user")
                self.pkg("verify")

                self.image_destroy()
                self.image_create(self.rurl)
                self.pkg("install singleuser", exit=1)

        def test_primordial_usergroup_exact_install(self):
                """Ensure that we can exact-install user and group actions in
                the same transaction as /etc/passwd, /etc/group, etc."""

                self.pkgsend_bulk(self.rurl, [self.basics0, self.singleuser])

                self.image_create(self.rurl)
                self.pkg("exact-install basics")
                self.pkg("verify")

                self.image_destroy()
                self.image_create(self.rurl)
                self.pkg("exact-install  basics singleuser")

        def test_ftpuser_install(self):
                """Make sure we correctly handle /etc/ftpd/ftpusers."""

                notftpuser = """
                open notftpuser@1
                add user username=animal group=root ftpuser=false
                close"""

                ftpuserexp = """
                open ftpuserexp@1
                add user username=fozzie group=root ftpuser=true
                close"""

                ftpuserimp = """
                open ftpuserimp@1
                add user username=gonzo group=root
                close"""

                self.pkgsend_bulk(self.rurl, (self.basics0, notftpuser,
                    ftpuserexp, ftpuserimp))
                self.image_create(self.rurl)

                self.pkg("install basics")

                # Add a user with ftpuser=false.  Make sure the user is added to
                # the file, and that the user verifies.
                self.pkg("install notftpuser")
                fpath = self.get_img_path() + "/etc/ftpd/ftpusers"
                self.assert_("animal\n" in file(fpath).readlines())
                self.pkg("verify notftpuser")

                # Add a user with an explicit ftpuser=true.  Make sure the user
                # is not added to the file, and that the user verifies.
                self.pkg("install ftpuserexp")
                self.assert_("fozzie\n" not in file(fpath).readlines())
                self.pkg("verify ftpuserexp")

                # Add a user with an implicit ftpuser=true.  Make sure the user
                # is not added to the file, and that the user verifies.
                self.pkg("install ftpuserimp")
                self.assert_("gonzo\n" not in file(fpath).readlines())
                self.pkg("verify ftpuserimp")

                # Put a user into the ftpusers file as shipped, then add that
                # user, with ftpuser=false.  Make sure the user remains in the
                # file, and that the user verifies.
                self.pkg("uninstall notftpuser")
                file(fpath, "a").write("animal\n")
                self.pkg("install notftpuser")
                self.assert_("animal\n" in file(fpath).readlines())
                self.pkg("verify notftpuser")

                # Put a user into the ftpusers file as shipped, then add that
                # user, with an explicit ftpuser=true.  Make sure the user is
                # stripped from the file, and that the user verifies.
                self.pkg("uninstall ftpuserexp")
                file(fpath, "a").write("fozzie\n")
                self.pkg("install ftpuserexp")
                self.assert_("fozzie\n" not in file(fpath).readlines())
                self.pkg("verify ftpuserexp")

                # Put a user into the ftpusers file as shipped, then add that
                # user, with an implicit ftpuser=true.  Make sure the user is
                # stripped from the file, and that the user verifies.
                self.pkg("uninstall ftpuserimp")
                file(fpath, "a").write("gonzo\n")
                self.pkg("install ftpuserimp")
                self.assert_("gonzo\n" not in file(fpath).readlines())
                self.pkg("verify ftpuserimp")

        def test_groupverify_install(self):
                """Make sure we correctly verify group actions when users have
                been added."""

                simplegroups = """
                open simplegroup@1
                add group groupname=muppets gid=100
                close
                open simplegroup2@1
                add group groupname=muppets2 gid=101
                close"""

                self.pkgsend_bulk(self.rurl, (self.basics0, simplegroups))
                self.image_create(self.rurl)

                self.pkg("install basics")
                self.pkg("install simplegroup")
                self.pkg("verify simplegroup")

                # add additional members to group & verify
                gpath = self.get_img_file_path("etc/group")
                gdata = file(gpath).readlines()
                gdata[-1] = gdata[-1].rstrip() + "kermit,misspiggy\n"
                file(gpath, "w").writelines(gdata)
                self.pkg("verify simplegroup")
                self.pkg("uninstall simplegroup")

                # verify that groups appear in gid order.
                self.pkg("install simplegroup simplegroup2")
                self.pkg("verify")
                gdata = file(gpath).readlines()
                self.assert_(gdata[-1].find("muppets2") == 0)
                self.pkg("uninstall simple*")
                self.pkg("install simplegroup2 simplegroup")
                gdata = file(gpath).readlines()
                self.assert_(gdata[-1].find("muppets2") == 0)

        def test_preexisting_group_install(self):
                """Make sure we correct any errors in pre-existing group actions"""
                simplegroup = """
                open simplegroup@1
                add group groupname=muppets gid=70
                close
                open simplegroup@2
                add dir path=/etc/muppet owner=root group=muppets mode=755
                add group groupname=muppets gid=70
                close"""

                self.pkgsend_bulk(self.rurl, (self.basics0, simplegroup))
                self.image_create(self.rurl)

                self.pkg("install basics")
                gpath = self.get_img_file_path("etc/group")
                gdata = file(gpath).readlines()
                gdata = ["muppets::1010:\n"] + gdata
                file(gpath, "w").writelines(gdata)
                self.pkg("verify")
                self.pkg("install simplegroup@1")
                self.pkg("verify simplegroup")
                # check # lines beginning w/ 'muppets' in group file
                gdata = file(gpath).readlines()
                self.assert_(
                    len([a for a in gdata if a.find("muppets") == 0]) == 1)

                # make sure we can add new version of same package
                self.pkg("update simplegroup")
                self.pkg("verify simplegroup")

        def test_missing_ownergroup_install(self):
                """test what happens when a owner or group is missing"""
                missing = """
                open missing_group@1
                add dir path=etc/muppet1 owner=root group=muppets mode=755
                close
                open missing_owner@1
                add dir path=etc/muppet2 owner=muppets group=root mode=755
                close
                open muppetsuser@1
                add user username=muppets group=bozomuppets uid=777
                close
                open muppetsuser@2
                add user username=muppets group=muppets uid=777
                close
                 open muppetsgroup@1
                add group groupname=muppets gid=777
                close
                """

                self.pkgsend_bulk(self.rurl, (self.basics0, missing))
                self.image_create(self.rurl)
                self.pkg("install basics")

                # try installing directory w/ a non-existing group
                self.pkg("install missing_group@1", exit=1)
                # try installing directory w/ a non-existing owner
                self.pkg("install missing_owner@1", exit=1)
                # try installing user w/ unknown group
                self.pkg("install muppetsuser@1", exit=1)
                # install group
                self.pkg("install muppetsgroup")
                # install working user & see if it all works.
                self.pkg("install muppetsuser@2")
                self.pkg("install missing_group@1")
                self.pkg("install missing_owner@1")
                self.pkg("verify")
                # edit group file to remove muppets group
                gpath = self.get_img_file_path("etc/group")
                gdata = file(gpath).readlines()
                file(gpath, "w").writelines(gdata[0:-1])

                # verify that we catch missing group
                # in both group and user actions
                self.pkg("verify muppetsgroup", 1)
                self.pkg("verify muppetsuser", 1)
                self.pkg("fix muppetsgroup", 0)
                self.pkg("verify muppetsgroup muppetsuser missing*")
                self.pkg("uninstall missing*")
                # try installing w/ broken group

                file(gpath, "w").writelines(gdata[0:-1])
                self.pkg("install missing_group@1", 1)
                self.pkg("fix muppetsgroup")
                self.pkg("install missing_group@1")
                self.pkg("install missing_owner@1")
                self.pkg("verify muppetsgroup muppetsuser missing*")

        def test_userverify_install(self):
                """Make sure we correctly verify user actions when the on-disk
                databases have been modified."""

                simpleusers = """
                open simpleuser@1
                add user username=misspiggy group=root gcos-field="& loves Kermie" login-shell=/bin/sh uid=5
                close
                open simpleuser2@1
                add user username=kermit group=root gcos-field="& loves mspiggy" login-shell=/bin/sh password=UP uid=6
                close
                open simpleuser2@2
                add user username=kermit group=root gcos-field="& loves mspiggy" login-shell=/bin/sh uid=6
                close"""


                self.pkgsend_bulk(self.rurl, (self.basics0, simpleusers))
                self.image_create(self.rurl)

                self.pkg("install basics")
                self.pkg("install simpleuser")
                self.pkg("verify simpleuser")

                ppath = self.get_img_path() + "/etc/passwd"
                pdata = file(ppath).readlines()
                spath = self.get_img_path() + "/etc/shadow"
                sdata = file(spath).readlines()

                def finderr(err):
                        self.assert_("\t\tERROR: " + err in self.output)

                # change a provided, empty-default field to something else
                pdata[-1] = "misspiggy:x:5:0:& loves Kermie:/:/bin/zsh"
                file(ppath, "w").writelines(pdata)
                self.pkg("verify simpleuser", exit=1)
                finderr("login-shell: '/bin/zsh' should be '/bin/sh'")
                self.pkg("fix simpleuser")
                self.pkg("verify simpleuser")

                # change a provided, non-empty-default field to the default
                pdata[-1] = "misspiggy:x:5:0:& User:/:/bin/sh"
                file(ppath, "w").writelines(pdata)
                self.pkg("verify simpleuser", exit=1)
                finderr("gcos-field: '& User' should be '& loves Kermie'")
                self.pkg("fix simpleuser")
                self.pkg("verify simpleuser")

                # change a non-provided, non-empty-default field to something
                # other than the default
                pdata[-1] = "misspiggy:x:5:0:& loves Kermie:/misspiggy:/bin/sh"
                file(ppath, "w").writelines(pdata)
                self.pkg("verify simpleuser", exit=1)
                finderr("home-dir: '/misspiggy' should be '/'")
                self.pkg("fix simpleuser")
                self.pkg("verify simpleuser")

                # add a non-provided, empty-default field
                pdata[-1] = "misspiggy:x:5:0:& loves Kermie:/:/bin/sh"
                sdata[-1] = "misspiggy:*LK*:14579:7:::::"
                file(ppath, "w").writelines(pdata)
                os.chmod(spath,
                    stat.S_IMODE(os.stat(spath).st_mode)|stat.S_IWUSR)
                file(spath, "w").writelines(sdata)
                self.pkg("verify simpleuser", exit=1)
                finderr("min: '7' should be '<empty>'")
                # fails fix since we don't repair shadow entries on purpose
                self.pkg("fix simpleuser")
                self.pkg("verify simpleuser", exit=1)
                finderr("min: '7' should be '<empty>'")

                # remove a non-provided, non-empty-default field
                pdata[-1] = "misspiggy:x:5:0:& loves Kermie::/bin/sh"
                sdata[-1] = "misspiggy:*LK*:14579::::::"
                file(ppath, "w").writelines(pdata)
                file(spath, "w").writelines(sdata)
                self.pkg("verify simpleuser", exit=1)
                finderr("home-dir: '' should be '/'")
                self.pkg("fix simpleuser")
                self.pkg("verify simpleuser")

                # remove a provided, non-empty-default field
                pdata[-1] = "misspiggy:x:5:0::/:/bin/sh"
                file(ppath, "w").writelines(pdata)
                self.pkg("verify simpleuser", exit=1)
                finderr("gcos-field: '' should be '& loves Kermie'")
                self.pkg("fix simpleuser")
                self.pkg("verify simpleuser")

                # remove a provided, empty-default field
                pdata[-1] = "misspiggy:x:5:0:& loves Kermie:/:"
                file(ppath, "w").writelines(pdata)
                self.pkg("verify simpleuser", exit=1)
                finderr("login-shell: '' should be '/bin/sh'")
                self.pkg("fix simpleuser")
                self.pkg("verify simpleuser")

                # remove the user from /etc/passwd
                pdata[-1] = "misswiggy:x:5:0:& loves Kermie:/:"
                file(ppath, "w").writelines(pdata)
                self.pkg("verify simpleuser", exit=1)
                finderr("login-shell: '<missing>' should be '/bin/sh'")
                finderr("gcos-field: '<missing>' should be '& loves Kermie'")
                finderr("group: '<missing>' should be 'root'")
                self.pkg("fix simpleuser")
                self.pkg("verify simpleuser")

                # remove the user completely
                pdata[-1] = "misswiggy:x:5:0:& loves Kermie:/:"
                sdata[-1] = "misswiggy:*LK*:14579::::::"
                file(ppath, "w").writelines(pdata)
                file(spath, "w").writelines(sdata)
                self.pkg("verify simpleuser", exit=1)
                finderr("username: '<missing>' should be 'misspiggy'")
                self.pkg("fix simpleuser")
                self.pkg("verify simpleuser")

                # change the password and show an error
                self.pkg("verify simpleuser")
                sdata[-1] = "misspiggy:NP:14579::::::"
                file(spath, "w").writelines(sdata)
                self.pkg("verify simpleuser", exit=1)
                finderr("password: 'NP' should be '*LK*'")
                self.pkg("fix simpleuser")
                self.pkg("verify simpleuser")

                # verify that passwords set to anything
                # other than '*LK*" or 'NP' in manifest
                # do not cause verify errors if changed.
                self.pkg("install --reject simpleuser simpleuser2@1")
                self.pkg("verify simpleuser2")
                pdata = file(ppath).readlines()
                sdata = file(spath).readlines()
                sdata[-1] = "kermit:$5$pWPEsjm2$GXjBRTjGeeWmJ81ytw3q1ah7QTaI7yJeRYZeyvB.Rp1:14579::::::"
                file(spath, "w").writelines(sdata)
                self.pkg("verify simpleuser2")

                # verify that upgrading package to version that implicitly
                # uses *LK* default causes password to change and that it
                # verifies correctly
                self.pkg("update simpleuser2@2")
                self.pkg("verify simpleuser2")
                sdata = file(spath).readlines()
                sdata[-1].index("*LK*")

                # ascertain that users are added in uid order when
                # installed at the same time.
                self.pkg("uninstall simpleuser2")
                self.pkg("install simpleuser simpleuser2")

                pdata = file(ppath).readlines()
                pdata[-1].index("kermit")

                self.pkg("uninstall simpleuser simpleuser2")
                self.pkg("install simpleuser2 simpleuser")

                pdata = file(ppath).readlines()
                pdata[-1].index("kermit")

        def test_minugid(self):
                """Ensure that an unspecified uid/gid results in the first
                unused."""

                self.minugid_helper("install")
                self.minugid_helper("exact-install")

        def minugid_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, (self.basics0, self.ugidtest))
                self.image_create(self.rurl)

                # This will lay down the sample passwd file, group file, etc.
                self.pkg("install basics")
                if install_cmd == "install":
                        self.pkg("install ugidtest")
                else:
                        self.pkg("exact-install basics ugidtest")
                passwd_file = file(os.path.join(self.get_img_path(),
                    "/etc/passwd"))
                for line in passwd_file:
                        if line.startswith("dummy"):
                                self.assert_(line.startswith("dummy:x:5:"))
                passwd_file.close()
                group_file = file(os.path.join(self.get_img_path(),
                    "/etc/group"))
                for line in group_file:
                        if line.startswith("dummy"):
                                self.assert_(line.startswith("dummy::5:"))
                group_file.close()

        def test_upgrade_with_user(self):
                """Ensure that we can add a user and change file ownership to
                that user in the same delta (mysql tripped over this early on
                in IPS development)."""

                self.upgrade_with_user_helper("install")
                self.upgrade_with_user_helper("exact-install")

        def upgrade_with_user_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, (self.basics0, self.basics1,
                    self.silver10, self.silver20, self.grouptest))
                self.image_create(self.rurl)
                self.pkg("install [email protected]")
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("list [email protected]")
                self.pkg("verify -v")
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("verify -v")

        def test_upgrade_garbage_passwd(self):
                self.upgrade_garbage_passwd_helper("install")
                self.upgrade_garbage_passwd_helper("exact-install")

        def upgrade_garbage_passwd_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, (self.basics0, self.singleuser))
                self.image_create(self.rurl)
                pwd_path = os.path.join(self.get_img_path(), "etc/passwd")
                # Put a garbage line in /etc/passwd, and make sure we can
                # install or exact-install, uninstall a user, and preserve the
                # garbage line.  Once with a blank line in the middle, once
                # with a non-blank line with too few fields, once with a
                # non-blank line with too many fields, and once with a blank
                # line at the end.
                for lineno, garbage in ((3, ""), (3, "garbage"),
                    (3, ":::::::::"), (100, "")):
                        garbage += "\n"
                        self.pkg("install basics")
                        with file(pwd_path, "r+") as pwd_file:
                                lines = pwd_file.readlines()
                                lines[lineno:lineno] = garbage
                                pwd_file.truncate(0)
                                pwd_file.seek(0)
                                pwd_file.writelines(lines)
                        if install_cmd == "install":
                                self.pkg("%s singleuser" % install_cmd)
                        else:
                                self.pkg("%s basics singleuser" % install_cmd)
                        with file(pwd_path) as pwd_file:
                                lines = pwd_file.readlines()
                                self.assert_(garbage in lines)
                        self.pkg("uninstall singleuser")
                        with file(pwd_path) as pwd_file:
                                lines = pwd_file.readlines()
                                self.assert_(garbage in lines)

                        self.pkg("uninstall '*'")

        def test_user_in_grouplist(self):
                """If a user is present in a secondary group list when the user
                is installed, the client shouldn't crash."""

                self.user_in_grouplist_helper("install")
                self.user_in_grouplist_helper("exact-install")

        def user_in_grouplist_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, (self.basics0, self.only_user10))
                self.image_create(self.rurl)
                self.pkg("install [email protected]")
                group_path = os.path.join(self.get_img_path(), "etc/group")
                with file(group_path, "r+") as group_file:
                        lines = group_file.readlines()
                        lines[0] = lines[0][:-1] + "Kermit" + "\n"
                        group_file.truncate(0)
                        group_file.seek(0)
                        group_file.writelines(lines)
                if install_cmd == "install":
                        self.pkg("install [email protected]")
                else:
                        self.pkg("exact-install [email protected] [email protected]")

        def test_invalid_open(self):
                """Send invalid package definitions (invalid fmris); expect
                failure."""

                for char in self.pkg_name_valid_chars["never"]:
                        invalid_name = "invalid%[email protected],5.11-0" % char
                        self.pkgsend(self.rurl, "open '%s'" % invalid_name,
                            exit=1)

                for char in self.pkg_name_valid_chars["after-first"]:
                        invalid_name = "%[email protected],5.11-0" % char
                        if char == "-":
                                cmd = "open -- '%s'" % invalid_name
                        else:
                                cmd = "open '%s'" % invalid_name
                        self.pkgsend(self.rurl, cmd, exit=1)

                        invalid_name = "invalid/%[email protected],5.11-0" % char
                        cmd = "open '%s'" % invalid_name
                        self.pkgsend(self.rurl, cmd, exit=1)

        def test_valid_open(self):
                """Send a series of valid packages; expect success."""

                for char in self.pkg_name_valid_chars["always"]:
                        valid_name = "%svalid%s/%spkg%[email protected],5.11-0" % (char,
                            char, char, char)
                        self.pkgsend(self.rurl, "open '%s'" % valid_name)
                        self.pkgsend(self.rurl, "close -A")

                for char in self.pkg_name_valid_chars["after-first"]:
                        valid_name = "v%salid%[email protected],5.11-0" % (char, char)
                        self.pkgsend(self.rurl, "open '%s'" % valid_name)
                        self.pkgsend(self.rurl, "close -A")

                for char in self.pkg_name_valid_chars["at-end"]:
                        valid_name = "validpkg%[email protected],5.11-0" % char
                        self.pkgsend(self.rurl, "open '%s'" % valid_name)
                        self.pkgsend(self.rurl, "close -A")

        def test_devlink(self):
                self.devlink_helper("install")
                self.devlink_helper("exact-install")

        def devlink_helper(self, install_cmd):
                # driver actions are not valid except on OpenSolaris
                if portable.util.get_canonical_os_name() != "sunos":
                        return

                self.pkgsend_bulk(self.rurl, (self.devicebase, self.devlink10,
                    self.devlink20))
                self.image_create(self.rurl)

                def readfile():
                        dlf = file(os.path.join(self.get_img_path(),
                            "etc/devlink.tab"))
                        dllines = dlf.readlines()
                        dlf.close()
                        return dllines

                def writefile(dllines):
                        dlf = file(os.path.join(self.get_img_path(),
                            "etc/devlink.tab"), "w")
                        dlf.writelines(dllines)
                        dlf.close()

                def assertContents(dllines, contents):
                        actual = re.findall("name=([^\t;]*)",
                            "\n".join(dllines), re.M)
                        self.assert_(set(actual) == set(contents))

                # Install
                self.pkg("install [email protected]")
                self.pkg("verify -v")

                dllines = readfile()

                # Verify that three entries got added
                self.assert_(len(dllines) == 3)

                # Verify that the tab character got written correctly
                self.assert_(dllines[0].find("\t") > 0)

                # Upgrade
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("verify -v")

                dllines = readfile()

                # Verify that there are four entries now
                self.assert_(len(dllines) == 4)

                # Verify they are what they should be
                assertContents(dllines, ["zerg2", "zorg", "borg", "zork"])

                # Remove
                self.pkg("uninstall devlinktest")
                self.pkg("verify -v")

                # Install again
                self.pkg("install [email protected]")

                # Diddle with it
                dllines = readfile()
                for i, line in enumerate(dllines):
                        if line.find("zerg") != -1:
                                dllines[i] = "type=ddi_pseudo;name=zippy\t\D\n"
                writefile(dllines)

                # Upgrade
                self.pkg("%s [email protected]" % install_cmd)

                # Verify that we spewed a message on upgrade
                self.assert_(self.output.find("not found") != -1)
                self.assert_(self.output.find("name=zerg") != -1)

                # Verify the new set
                dllines = readfile()
                self.assert_(len(dllines) == 5)
                assertContents(dllines,
                    ["zerg2", "zorg", "borg", "zork", "zippy"])

                self.pkg("uninstall devlinktest")

                # Null out the "zippy" entry
                writefile([])

                # Install again
                self.pkg("install [email protected]")

                # Diddle with it
                dllines = readfile()
                for i, line in enumerate(dllines):
                        if line.find("zerg") != -1:
                                dllines[i] = "type=ddi_pseudo;name=zippy\t\D\n"
                writefile(dllines)

                # Remove
                self.pkg("uninstall devlinktest")

                # Verify that we spewed a message on removal
                self.assert_(self.output.find("not found") != -1)
                self.assert_(self.output.find("name=zerg") != -1)

                # Verify that the one left behind was the one we overwrote.
                dllines = readfile()
                self.assert_(len(dllines) == 1)
                assertContents(dllines, ["zippy"])

                # Null out the "zippy" entry, but add the "zerg" entry
                writefile(["type=ddi_pseudo;name=zerg\t\D\n"])

                # Install ... again
                self.pkg("install [email protected]")

                # Make sure we didn't get a second zerg line
                dllines = readfile()
                self.assertTrue(len(dllines) == 3, msg=dllines)
                assertContents(dllines, ["zerg", "borg", "warg"])

                # Now for the same test on upgrade
                dllines.append("type=ddi_pseudo;name=zorg\t\D\n")
                writefile(dllines)

                self.pkg("%s [email protected]" % install_cmd)
                dllines = readfile()
                self.assertTrue(len(dllines) == 4, msg=dllines)
                assertContents(dllines, ["zerg2", "zorg", "borg", "zork"])

        def test_driver_aliases_upgrade(self):
                """Make sure that aliases properly appear and disappear on
                upgrade.  This is the result of a bug in update_drv, but it's
                not a bad idea to test some of this ourselves."""

                self.driver_aliases_upgrade_helper("install")
                self.driver_aliases_upgrade_helper("exact-install")

        def driver_aliases_upgrade_helper(self, install_cmd):
                # driver actions are not valid except on OpenSolaris
                if portable.util.get_canonical_os_name() != "sunos":
                        return

                self.pkgsend_bulk(self.rurl, [self.devicebase, self.devalias10,
                    self.devalias20])

                self.image_create(self.rurl)
                self.pkg("%s devicebase devalias@1" % install_cmd)
                self.pkg("update devalias")
                self.pkg("verify devalias")

                daf = open(os.path.join(self.get_img_path(),
                    "etc/driver_aliases"))
                dalines = daf.readlines()
                daf.close()

                self.assert_(len(dalines) == 1, msg=dalines)
                self.assert_(",1234" not in dalines[0])
                self.assert_(",4321" not in dalines[0])
                self.assert_(",5555" in dalines[0])

        def test_driver_aliases_move(self):
                """Make sure that an alias can be moved from one driver action
                to another."""

                self.driver_aliases_move_helper("install")
                self.driver_aliases_move_helper("exact-install")

        def driver_aliases_move_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, [self.devicebase,
                    self.devaliasmove10, self.devaliasmove20])

                self.image_create(self.rurl)
                self.pkg("%s devicebase devaliasmove@1" % install_cmd)
                self.pkg("update devaliasmove")
                self.assert_("pci8086,5555" not in self.output)

        def test_uninstall_without_perms(self):
                """Verify uninstall fails as expected for unprivileged users."""

                pkg_list = [self.foo10, self.only_attr10, self.only_depend10,
                    self.only_directory10, self.only_file10,
                    self.only_group10, self.only_hardlink10, self.only_legacy10,
                    self.only_license10, self.only_link10, self.only_user10]

                # driver actions are not valid except on OpenSolaris
                if portable.util.get_canonical_os_name() == 'sunos':
                        pkg_list += [self.only_driver10]

                self.pkgsend_bulk(self.rurl, pkg_list + [
                    self.devicebase + self.basics0 + self.basics1])

                self.image_create(self.rurl)

                name_pat = re.compile("^\s+open\s+(\S+)\@.*$")

                def __manually_check_deps(name, install=True, exit=0):
                        cmd = ["install", "--no-refresh"]
                        if not install:
                                cmd = ["uninstall"]
                        if name == "only_depend" and not install:
                                self.pkg(cmd + ["foo"], exit=exit)
                        elif name == "only_driver":
                                self.pkg(cmd + ["devicebase"], exit=exit)
                        elif name == "only_group":
                                self.pkg(cmd + ["basics"], exit=exit)
                        elif name == "only_hardlink":
                                self.pkg(cmd + ["only_file"], exit=exit)
                        elif name == "only_user":
                                if install:
                                        self.pkg(cmd + ["basics"], exit=exit)
                                        self.pkg(cmd + ["only_group"],
                                            exit=exit)
                                else:
                                        self.pkg(cmd + ["only_group"],
                                            exit=exit)
                                        self.pkg(cmd + ["basics"], exit=exit)
                for p in pkg_list:
                        name_mat = name_pat.match(p.splitlines()[1])
                        pname = name_mat.group(1)
                        __manually_check_deps(pname, exit=[0, 4])
                        self.pkg(["install", "--no-refresh", pname],
                            su_wrap=True, exit=1)
                        self.pkg(["install", pname], su_wrap=True,
                            exit=1)
                        self.pkg(["install", "--no-refresh", pname])
                        self.pkg(["uninstall", pname], su_wrap=True,
                            exit=1)
                        self.pkg(["uninstall", pname])
                        __manually_check_deps(pname, install=False)

                for p in pkg_list:
                        name_mat = name_pat.match(p.splitlines()[1])
                        pname = name_mat.group(1)
                        __manually_check_deps(pname, exit=[0, 4])
                        self.pkg(["install", "--no-refresh", pname])

                for p in pkg_list:
                        self.pkgsend_bulk(self.rurl, p)
                self.pkgsend_bulk(self.rurl, (self.devicebase, self.basics0,
                    self.basics1))

                # Modifying operations require permissions needed to create and
                # manage lock files.
                self.pkg(["update", "--no-refresh"], su_wrap=True, exit=1)

                self.pkg(["refresh"])
                self.pkg(["update"], su_wrap=True, exit=1)
                # Should fail since user doesn't have permission to refresh
                # publisher metadata.
                self.pkg(["refresh", "--full"], su_wrap=True, exit=1)
                self.pkg(["refresh", "--full"])
                self.pkg(["update", "--no-refresh"], su_wrap=True,
                    exit=1)
                self.pkg(["update"])

        def test_bug_3222(self):
                """ Verify that a timestamp of '0' for a passwd file will not
                    cause further package operations to fail.  This can happen
                    when there are time synchronization issues within a virtual
                    environment or in other cases. """

                self.pkgsend_bulk(self.rurl, (self.basics0, self.only_user10,
                    self.only_user_file10, self.only_group10,
                    self.only_group_file10, self.grouptest, self.usertest10))
                self.image_create(self.rurl)
                fname = os.path.join(self.get_img_path(), "etc", "passwd")
                self.pkg("install basics")

                # This should work regardless of whether a user is installed
                # at the same time as the file in a package, or if the user is
                # installed first and then files owned by that user are
                # installed.
                plists = [["grouptest", "usertest"],
                    ["only_user", "only_user_file"],
                    ["only_group", "only_group_file"]]
                for plist in plists:
                        for pname in plist:
                                os.utime(fname, (0, 0))
                                self.pkg("install %s" % pname)
                                self.pkg("verify")

                        for pname in reversed(plist):
                                os.utime(fname, (0, 0))
                                self.pkg("uninstall %s" % pname)
                                self.pkg("verify")

        def test_bad_hardlinks(self):
                """A couple of bogus hard link target tests."""

                self.bad_hardlinks_helper("install")
                self.bad_hardlinks_helper("exact-install")

        def bad_hardlinks_helper(self, install_cmd):
                self.pkgsend_bulk(self.rurl, (self.badhardlink1,
                    self.badhardlink2))
                self.image_create(self.rurl)

                # A package which tries to install a hard link to a target that
                # doesn't exist shouldn't stack trace, but exit sanely.
                self.pkg("%s badhardlink1" % install_cmd, exit=1)

                # A package which tries to install a hard link to a target
                # specified as an absolute path should install that link
                # relative to the image root.
                self.pkg("%s badhardlink2" % install_cmd)
                ino1 = os.stat(os.path.join(self.get_img_path(), "foo")).st_ino
                ino2 = os.stat(os.path.join(self.get_img_path(), "etc/motd")).st_ino
                self.assert_(ino1 == ino2)

        def test_legacy(self):
                self.pkgsend_bulk(self.rurl,
                    (self.csu1, self.csu1_2, self.csu2, self.csu2_2,
                    self.csu3, self.csu3_2))
                self.image_create(self.rurl)

                self.pkg("install csu1@1 csu2@1 csu3@1")

                # Make sure we installed one and only one pkginfo file, and with
                # the correct information.
                vsp = self.get_img_file_path("var/sadm/pkg")
                pi = os.path.join(vsp, "SUNWcsu/pkginfo")
                pi2 = os.path.join(vsp, "SUNWcsu/pkginfo.2")
                pi3 = os.path.join(vsp, "SUNWcsu/pkginfo.3")
                self.assert_(os.path.exists(pi), "pkginfo doesn't exist")
                self.file_contains(pi, "VERSION=11.11,REV=2009.11.11")
                self.assert_(not os.path.exists(pi2), "pkginfo.2 exists")
                self.assert_(not os.path.exists(pi3), "pkginfo.3 exists")
                # Create the hardlinks as we'd have for the old refcounting
                # system.
                os.link(pi, pi2)
                os.link(pi, pi3)

                # Make sure that upgrading the actions modifies the pkginfo file
                # correctly, and that the hardlinks go away.
                self.pkg("update")
                self.file_contains(pi, "VERSION=11.11,REV=2010.11.11")
                self.assert_(not os.path.exists(pi2), "pkginfo.2 exists")
                self.assert_(not os.path.exists(pi3), "pkginfo.3 exists")

                # Start over, but this time "break" the hardlinks.
                self.pkg("uninstall -vvv \*")
                self.pkg("install csu1@1 csu2@1 csu3@1")
                shutil.copy(pi, pi2)
                shutil.copy(pi, pi3)
                self.pkg("update")
                self.file_contains(pi, "VERSION=11.11,REV=2010.11.11")
                self.assert_(not os.path.exists(pi2), "pkginfo.2 exists")
                self.assert_(not os.path.exists(pi3), "pkginfo.3 exists")


class TestDependencies(pkg5unittest.SingleDepotTestCase):
        # Only start/stop the depot once (instead of for every test)
        persistent_setup = True

        pkg10 = """
            open [email protected],5.11-0
            add depend type=optional fmri=pkg:/pkg2
            close
        """

        pkg20 = """
            open [email protected],5.11-0
            close
        """

        pkg11 = """
            open [email protected],5.11-0
            add depend type=optional fmri=pkg:/[email protected]
            close
        """

        pkg21 = """
            open [email protected],5.11-0
            close
        """

        pkg30 = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg:/[email protected]
            close
        """

        pkg40 = """
            open [email protected],5.11-0
            add depend type=exclude fmri=pkg:/[email protected]
            close
        """

        pkg50 = """
            open [email protected],5.11-0
            add depend type=exclude fmri=pkg:/[email protected]
            add depend type=require fmri=pkg:/[email protected]
            close
        """

        pkg505 = """
            open [email protected],5.11-0
            add depend type=exclude fmri=pkg:/[email protected]
            add depend type=require fmri=pkg:/[email protected]
            close
        """
        pkg51 = """
            open [email protected],5.11-0
            add depend type=exclude fmri=pkg:/[email protected]
            add depend type=exclude fmri=pkg:/pkg2
            add depend type=require fmri=pkg:/[email protected]
            close
        """
        pkg60 = """
            open [email protected],5.11-0
            add depend type=exclude fmri=pkg:/[email protected]
            close
        """

        pkg61 = """
            open [email protected],5.11-0
            close
        """

        bug_18653 = """
            open [email protected],5.11-0
            add depend type=incorporate [email protected]
            close
            open [email protected],5.11-0
            add depend type=incorporate [email protected]
            close
            open [email protected],5.11-0
            add depend type=incorporate [email protected]
            add depend type=incorporate [email protected]
            close
            open [email protected],5.11-0
            add depend type=incorporate [email protected]
            add depend type=incorporate [email protected]
            close
            open [email protected],5.11-0
            add depend type=require fmri=osnet-incorporation
            add depend type=conditional predicate=perl-510 [email protected]
            close
            open [email protected],5.11-0
            add depend type=require fmri=osnet-incorporation
            add depend type=conditional predicate=perl-510 [email protected]
            close
            open [email protected],5.11-0
            add depend type=require fmri=osnet-incorporation
            add depend type=require [email protected]
            close
            open [email protected],5.11-0
            close
            open [email protected],5.11-0
            close
        """

        pkg70 = """
            open [email protected],5.11-0
            add depend type=conditional predicate=pkg:/[email protected] fmri=pkg:/[email protected]
            close
        """

        pkg80 = """
            open [email protected],5.11-0
            add depend type=require-any fmri=pkg:/[email protected] fmri=pkg:/[email protected] fmri=pkg:/nonsuch
            close
        """

        pkg81 = """
            open [email protected],5.11-0
            add depend type=require-any fmri=pkg:/[email protected] fmri=pkg:/[email protected] fmri=pkg:/nonsuch
            close
        """

        pkg90 = """
            open [email protected],5.11-0
            close
        """

        pkg91 = """
            open [email protected],5.11-0
            close
        """

        pkg100 = """
            open [email protected],5.11-0
            close
        """

        pkg101 = """
            open [email protected],5.11-0
            close
        """

        pkg102 = """
            open [email protected],5.11-0
            add depend type=origin [email protected],5.11-0
            close
        """

        pkg110 = """
            open [email protected],5.11-0
            add depend type=origin root-image=true [email protected]
            close
        """
        pkg111 = """
            open [email protected],5.11-0
            add depend type=origin root-image=true [email protected]
            close
        """

        pkg121 = """
            open [email protected],5.11-0
        """
        pkg121 += "add depend type=parent fmri=%s" % \
            pkg.actions.depend.DEPEND_SELF
        pkg121 += """
            close
        """

        pkg122 = """
            open [email protected],5.11-0
        """
        pkg122 += "add depend type=parent fmri=%s" % \
            pkg.actions.depend.DEPEND_SELF
        pkg122 += """
            close
        """

        pkg123 = """
            open [email protected],5.11-0
        """
        pkg123 += "add depend type=parent fmri=%s" % \
            pkg.actions.depend.DEPEND_SELF
        pkg123 += """
            close
        """

        pkg132 = """
            open [email protected],5.11-0
            add depend type=parent [email protected],5.11-0
            close
        """

        pkg142 = """
            open [email protected],5.11-0
            add depend type=parent [email protected],5.11-0
            add depend type=parent [email protected],5.11-0
            close
        """

        pkg_nosol = """
            open [email protected],5.11-0
            add depend type=require-any fmri=pkg:/pkg-nosol-B fmri=pkg:/pkg-nosol-C
            add depend type=require fmri=pkg:/pkg-nosol-D
            close
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            close
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            close
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            close
            open [email protected],5.11-0
            close
            open [email protected],5.11-0
            close
        """

        pkg_renames = """
            open [email protected],5.11-0
            add depend type=require fmri=pkg_rename
            close
            open [email protected],5.11-0
            add set name=pkg.renamed value=true
            add depend type=require fmri=pkg:/pkg_bar
            close
            open [email protected],5.11-0
            close
            open [email protected],5.11-0
            add set name=pkg.renamed value=true
            add depend type=require fmri=system/[email protected]
            close
            open system/[email protected],5.11-0
            close
        """

        pkgSUNWcs075 = """
            open [email protected]
            close
        """

        leaf_template = """
            open pkg%s%s@%s,5.11-0
            add depend type=require fmri=pkg:/%s_incorp%s
            close
        """
        install_hold = "add set name=pkg.depend.install-hold value=test"

        leaf_expansion = [
                ("A","_0", "1.0", "A", ""),
                ("A","_1", "1.0", "A", ""),
                ("A","_2", "1.0", "A", ""),
                ("A","_3", "1.0", "A", ""),

                ("B","_0", "1.0", "B", ""),
                ("B","_1", "1.0", "B", ""),
                ("B","_2", "1.0", "B", ""),
                ("B","_3", "1.0", "B", ""),

                ("A","_0", "1.1", "A", "@1.1"),
                ("A","_1", "1.1", "A", "@1.1"),
                ("A","_2", "1.1", "A", "@1.1"),
                ("A","_3", "1.1", "A", "@1.1"),

                ("B","_0", "1.1", "B", "@1.1"),
                ("B","_1", "1.1", "B", "@1.1"),
                ("B","_2", "1.1", "B", "@1.1"),
                ("B","_3", "1.1", "B", "@1.1"),

                ("A","_0", "1.2", "A", "@1.2"),
                ("A","_1", "1.2", "A", "@1.2"),
                ("A","_2", "1.2", "A", "@1.2"),
                ("A","_3", "1.2", "A", "@1.2"),

                ("B","_0", "1.2", "B", "@1.2"),
                ("B","_1", "1.2", "B", "@1.2"),
                ("B","_2", "1.2", "B", "@1.2"),
                ("B","_3", "1.2", "B", "@1.2"),

                ("A","_0", "1.3", "A", ""),
                ("A","_1", "1.3", "A", ""),
                ("A","_2", "1.3", "A", ""),
                ("A","_3", "1.3", "A", ""),

                ("B","_0", "1.3", "B", ""),
                ("B","_1", "1.3", "B", ""),
                ("B","_2", "1.3", "B", ""),
                ("B","_3", "1.3", "B", "")
                ]

        incorps = [ """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            close
        """,

        """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            close
        """,

        """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add set name=pkg.depend.install-hold value=test
            close
        """,

        """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add set name=pkg.depend.install-hold value=test
            close
        """,

        """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add set name=pkg.depend.install-hold value=test.A
            close
        """,

        """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add set name=pkg.depend.install-hold value=test.B
            close
        """,

        """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add set name=pkg.depend.install-hold value=test.A
            close
        """,

        """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add set name=pkg.depend.install-hold value=test.B
            close
        """,

        """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add set name=pkg.depend.install-hold value=test
            close
        """,

        """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add set name=pkg.depend.install-hold value=test
            close
        """,

        """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=incorporate fmri=pkg:/[email protected]
            add set name=pkg.depend.install-hold value=test
            close
        """,
        """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            add depend type=exclude fmri=pkg:/pkgB_0
            add set name=pkg.depend.install-hold value=test
            close
        """
        ]

        bug_7394_incorp = """
            open [email protected],5.11-0
            add depend type=incorporate fmri=pkg:/[email protected]
            close
        """

        exclude_group = """
            open network/[email protected]
            close
            open [email protected]
            add depend type=group fmri=network/rsync
            close
            open utility/[email protected]
            add depend type=exclude fmri=network/rsync
            close
        """

        def setUp(self):
                pkg5unittest.SingleDepotTestCase.setUp(self, image_count=2)
                self.pkgsend_bulk(self.rurl, (self.pkg10, self.pkg20,
                    self.pkg11, self.pkg21, self.pkg30, self.pkg40, self.pkg50,
                    self.pkg505, self.pkg51, self.pkg60, self.pkg61,
                    self.bug_18653, self.pkg70, self.pkg80, self.pkg81,
                    self.pkg90, self.pkg91, self.bug_7394_incorp, self.pkg100,
                    self.pkg101, self.pkg102, self.pkg110, self.pkg111,
                    self.pkg121, self.pkg122, self.pkg123, self.pkg132,
                    self.pkg142, self.pkg_nosol, self.pkg_renames,
                    self.pkgSUNWcs075, self.exclude_group))

                self.leaf_pkgs = []
                for t in self.leaf_expansion:
                        self.leaf_pkgs.extend(self.pkgsend_bulk(self.rurl,
                            self.leaf_template % t))

                self.incorp_pkgs = []
                for i in self.incorps:
                        self.incorp_pkgs.extend(self.pkgsend_bulk(self.rurl, i))

        def test_rename_matching(self):
                """Verify install or exact-install won't fail with a multiple
                match error for a renamed package that shares a common
                basename."""

                self.rename_matching("install")
                self.rename_matching("exact-install")

        def rename_matching(self, install_cmd):
                self.image_create(self.rurl)
                self.pkg("%s trusted" % install_cmd)
                self.pkg("info system/trusted")

        def test_require_dependencies(self):
                """ exercise require dependencies """

                self.require_dependencies_helper("install")
                self.require_dependencies_helper("exact-install")

        def require_dependencies_helper(self, install_cmd):
                self.image_create(self.rurl)
                self.pkg("install [email protected]")
                self.pkg("verify  [email protected]")
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("verify  [email protected] [email protected]")

        def test_exclude_group_install(self):
                """Verify that a simultaneous exclude and group dependency on
                the same package is handled gracefully."""

                self.image_create(self.rurl)

                # These should fail (gracefully) because my-rsync packages
                # excludes network/rsync which is a group dependency of
                # gold-server package.
                self.pkg("install network/rsync gold-server my-rsync", exit=1)

                self.pkg("install network/rsync")
                self.pkg("install gold-server my-rsync", exit=1)
                self.pkg("uninstall '*'")

                # This should succeed because network/rsync dependency is not
                # installed.
                self.pkg("avoid network/rsync")
                self.pkg("install -nv gold-server my-rsync")

                # This will install network/rsync and remove it from the avoid
                # list.
                self.pkg("install network/rsync")

                # This should succeed because network/rsync will be removed and
                # placed on avoid list as part of operation.
                self.pkg("install --reject network/rsync gold-server my-rsync")

                # Now remove gold-server and then verify install will fail.
                self.pkg("uninstall gold-server")
                self.pkg("unavoid network/rsync")
                # No solution as there's no installed constraining package and
                # user didn't provide sufficient input.
                self.pkg("install gold-server", assert_solution=False, exit=1)

        def test_exclude_dependencies_install(self):
                """ exercise exclude dependencies """

                self.image_create(self.rurl)
                # install pkg w/ exclude dep.
                self.pkg("install [email protected]")
                self.pkg("verify  [email protected]")
                # install pkg that is allowed by dep
                self.pkg("install [email protected]")
                self.pkg("verify  [email protected]")
                # try to install disallowed pkg
                self.pkg("install [email protected]", exit=1)
                self.pkg("uninstall '*'")
                # install pkg
                self.pkg("install [email protected]")
                # try to install pkg exclude dep on already
                # installed pkg
                self.pkg("install [email protected]", exit=1)
                self.pkg("uninstall '*'")
                # install a package w/ both exclude
                # and require dependencies
                self.pkg("install pkg5")
                self.pkg("verify [email protected] [email protected] ")
                self.pkg("uninstall '*'")
                # pick pkg to install that fits constraint
                # of already installed pkg
                self.pkg("install pkg2")
                self.pkg("install pkg5")
                self.pkg("verify [email protected] [email protected] pkg2")
                self.pkg("uninstall '*'")
                # install a package that requires updating
                # existing package to avoid exclude
                # dependency
                self.pkg("install [email protected]")
                self.pkg("install [email protected]")
                self.pkg("verify [email protected] [email protected]")
                self.pkg("uninstall '*'")
                # try to install two incompatible pkgs
                self.pkg("install [email protected] [email protected]", exit=1)

        def test_exclude_dependencies_exact_install(self):
                """ exercise exclude dependencies """

                self.image_create(self.rurl)
                # Install pkg w/ exclude dep.
                self.pkg("install [email protected]")
                self.pkg("verify  [email protected]")

                # Exact-install should ignore exclude dependencies,
                # except user specifies both pkg name in command line.
                self.pkg("exact-install [email protected]")
                self.pkg("uninstall '*'")

                # Exact-install a package w/ both exclude
                # and require dependencies.
                self.pkg("exact-install pkg5")
                self.pkg("verify [email protected] [email protected] ")
                self.pkg("uninstall '*'")
                # pick pkg to exact-install that fits constraint.
                self.pkg("exact-install pkg2 pkg5")
                self.pkg("verify [email protected] [email protected] pkg2")
                self.pkg("uninstall '*'")
                # Exact- install two packages with proper version selected for
                # one package based on the version of the other package.
                self.pkg("exact-install pkg6 [email protected]")
                self.pkg("verify [email protected] [email protected]")
                self.pkg("uninstall '*'")
                # try to exact-install two incompatible pkgs
                self.pkg("exact-install [email protected] [email protected]", exit=1)

        def test_optional_dependencies_install(self):
                """ check to make sure that optional dependencies are enforced
                """

                self.image_create(self.rurl)
                self.pkg("install [email protected]")

                # pkg2 is optional, it should not have been installed
                self.pkg("list pkg2", exit=1)

                self.pkg("install [email protected]")

                # this should install [email protected] and upgrade pkg2 to [email protected]
                self.pkg("install pkg1")
                self.pkg("list [email protected]")

                self.pkg("uninstall pkg2")
                self.pkg("list pkg2", exit=1)
                # this should not install [email protected] because of the optional
                # dependency in pkg1
                self.pkg("list [email protected]")
                self.pkg("install [email protected]", exit=1)

        def test_optional_dependencies_exact_install(self):
                """ check to make sure that optional dependencies are enforced
                """

                self.image_create(self.rurl)
                self.pkg("exact-install [email protected]")

                # pkg2 is optional, it should not have been installed
                self.pkg("list pkg2", exit=1)

                self.pkg("install [email protected]")

                # Verify with exact-install, the optional dependency package
                # should be removed.
                self.pkg("exact-install pkg1")
                self.pkg("list [email protected]", exit=1)
                self.pkg("list [email protected]")

                # Verify exact-install [email protected] should succeed and pkg1 should
                # be removed.
                self.pkg("exact-install [email protected]")
                self.pkg("list pkg1", exit=1)

        def test_incorporation_dependencies_install(self):
                """ shake out incorporation dependencies """

                self.image_create(self.rurl)
                # simple pkg requiring controlling incorp
                # should control pkgA_1 as well
                self.pkg("install -v [email protected] pkgA_1")
                self.pkg("list")
                self.pkg("verify [email protected] [email protected] [email protected]")
                self.pkg("install [email protected]")
                self.pkg("list [email protected] [email protected] [email protected]")
                self.pkg("uninstall '*'")
                # try nested incorporations
                self.pkg("install -v [email protected] pkgA_0 pkgB_0")
                self.pkg("list")
                self.pkg("list [email protected] [email protected] [email protected] [email protected] [email protected]")
                # try to break incorporation
                self.pkg("install -v [email protected]", exit=1) # fixed by [email protected]
                # try update (using '*' which also checks that the update all
                # path is used when '*' is specified)
                self.pkg("update -v '*'")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]")
                self.pkg("uninstall '*'")
                # what happens when incorporation specified
                # a package that isn't in the catalog
                self.pkg("install bug_7394_incorp")
                self.pkg("install pkg1", exit=1)
                self.pkg("uninstall '*'")
                # test pkg.depend.install-hold feature
                self.pkg("install -v [email protected]  pkgA_1")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]")
                # next attempt will fail because incorporations prevent motion
                # even though explicit dependency exists from pkg to
                # incorporation.
                self.pkg("install [email protected]", exit=1)
                # test to see if we could install both; presence of incorp
                # causes relaxation of pkg.depend.install-hold and also test
                # that parsable output works when -n is used
                self.pkg("install -n --parsable=0 [email protected] [email protected]")
                self.assertEqualParsable(self.output, change_packages=[
                    [self.incorp_pkgs[2], self.incorp_pkgs[4]],
                    [self.leaf_pkgs[9], self.leaf_pkgs[17]]])
                # this attempt also succeeds because pkg.depend.install-hold is
                # relaxed since A_incorp is on command line
                self.pkg("install [email protected]")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]")
                # now demonstrate w/ version 1.2 subincorps that master incorp
                # prevents upgrade since pkg.depend.install-hold of master != other incorps
                self.pkg("install [email protected]")
                self.pkg("install [email protected]", exit=1)
                self.pkg("install [email protected]")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]")

        def test_incorporation_dependencies_exact_install(self):
                """ shake out incorporation dependencies """

                self.image_create(self.rurl)
                # Simple pkg requiring controlling incorp
                # should control pkgA_1 as well.
                self.pkg("exact-install -v [email protected] pkgA_1")
                self.pkg("list")
                self.pkg("verify [email protected] [email protected] [email protected]")

                # Verify exact-install will upgrade pkgA_0 and A_incorp to 1.1
                #, and uninstall pkgA_1.
                self.pkg("exact-install [email protected]")
                self.pkg("list [email protected] [email protected]")
                self.pkg("uninstall '*'")

                # Try nested incorporations.
                self.pkg("exact-install -v [email protected] pkgA_0 pkgB_0")
                self.pkg("list [email protected] [email protected] [email protected] [email protected] [email protected]")
                # Try to break incorporation.
                self.pkg("exact-install -v [email protected] [email protected]", exit=1) # fixed by [email protected]

                # Only specify [email protected] should succeed. install holds
                # should be ignored.
                self.pkg("exact-install -v [email protected]")
                self.pkg("list [email protected]")
                self.pkg("uninstall '*'")
                # what happens when incorporation specified
                # a package that isn't in the catalog.
                self.pkg("exact-install pkg1 bug_7394_incorp", exit=1)
                # test pkg.depend.install-hold feature.
                self.pkg("exact-install -v [email protected]  pkgA_1")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]")

        def test_conditional_dependencies_install(self):
                """Get conditional dependencies working"""
                self.image_create(self.rurl)
                self.pkg("install [email protected]")
                self.pkg("verify")
                self.pkg("list [email protected]", exit=1) # should not be here
                self.pkg("install -v [email protected]")      # older version...
                self.pkg("verify")
                self.pkg("list [email protected]", exit=1)
                self.pkg("install -v [email protected]")      # this triggers conditional dependency
                self.pkg("verify")
                self.pkg("list [email protected] [email protected] [email protected]") # everyone is present
                self.pkg("uninstall '*'")

                self.pkg("install [email protected]")  # install trigger
                self.pkg("verify")
                self.pkg("install [email protected]")  # install pkg
                self.pkg("list [email protected] [email protected] [email protected]") # all here again
                self.pkg("verify")
                self.pkg("uninstall '*'")

                # Test bug 18653
                self.pkg("install [email protected] sun-solaris "
                    "perl-510 sun-solaris-510")
                # Uninstall should fail because sun-solaris conditional
                # dependency requires sun-solaris-510.
                self.pkg("uninstall sun-solaris-510", exit=1)
                # Uninstalling both the predicate and the target of the
                # conditional dependency should work.
                self.pkg("uninstall perl-510 sun-solaris-510")
                self.pkg("install perl-510")
                # Check that reject also works.
                self.pkg("update --reject perl-510 --reject sun-solaris-510")
                self.pkg("uninstall '*'")

                # Verify that if the predicate of a conditional can be
                # installed, but the consequent cannot, the package delivering
                # the conditional dependency can still be installed.
                self.pkg("install -v entire [email protected] "
                    "sun-solaris")

                # Verify that the package incorporating a package that delivers
                # a conditional for a consequent that cannot be installed can be
                # removed.
                self.pkg("uninstall -v entire")

        def test_conditional_dependencies_exact_install(self):
                """Get conditional dependencies working."""

                self.image_create(self.rurl)
                # Presenting [email protected] and [email protected] in the command line should
                # trigger conditional dependency.
                self.pkg("exact-install -v [email protected] [email protected]")
                self.pkg("verify")
                self.pkg("list [email protected] [email protected] [email protected]")
                self.pkg("uninstall '*'")

                # If  only [email protected] present in the command line, [email protected]
                # should be removed and [email protected] should not be installed.
                self.pkg("install [email protected]")
                self.pkg("verify")
                self.pkg("exact-install [email protected]")
                self.pkg("list [email protected]")
                self.pkg("list [email protected]", exit=1)
                self.pkg("list [email protected]", exit=1)
                self.pkg("verify")
                self.pkg("uninstall '*'")

        def test_require_any_dependencies_install(self):
                """Get require-any dependencies working"""
                self.image_create(self.rurl)

                # test to see if solver will fail gracefully when no solution is
                # possible and a require-any dependency is involved
                self.pkg("install -vvv pkg-nosol-A pkg-nosol-E",
                    assert_solution=False, exit=1)

                # test to see if solver will pick one
                self.pkg("install [email protected]")  # install pkg
                self.pkg("verify")
                self.pkg("list [email protected] [email protected] [email protected]", exit=3)
                self.pkg("uninstall '*'")

                # test to see if solver will be happy w/ renamed packages,
                # already installed dependencies.
                self.pkg("install pkg:/[email protected]")
                self.pkg("install pkg_need_rename")
                self.pkg("install [email protected]")
                self.pkg("verify")
                self.pkg("list [email protected] [email protected]")
                self.pkg("uninstall '*'")

                # test to see if solver will install new verion of existing
                # package rather than add new package
                self.pkg("install [email protected] [email protected]")  # install pkg
                self.pkg("verify")
                self.pkg("list [email protected] [email protected]")
                self.pkg("list [email protected]", exit=1)
                self.pkg("install pkg8 pkg9") # will fail w/o pkg9 on list
                self.pkg("verify")
                self.pkg("list [email protected] [email protected]")
                self.pkg("uninstall '*'")

                # see if update works the same way
                self.pkg("install [email protected] [email protected]")  # install pkg
                self.pkg("image-update")
                self.pkg("list [email protected] [email protected]")
                self.pkg("uninstall '*'")

                # test to see if uninstall is clever enough
                self.pkg("install [email protected] [email protected]")  # install pkg
                self.pkg("verify")
                self.pkg("list [email protected] [email protected]")
                self.pkg("uninstall [email protected]")
                self.pkg("list [email protected]")
                self.pkg("verify")

        def test_require_any_dependencies_exact_install(self):
                """Get require-any dependencies working"""
                self.image_create(self.rurl)

                # Test to see if solver will fail gracefully when no solution is
                # possible and a require-any dependency is involved.
                self.pkg("exact-install -v pkg-nosol-A pkg-nosol-E",
                    assert_solution=False, exit=1)

                # Test to see if solver will pick one.
                self.pkg("exact-install [email protected]")
                self.pkg("verify")
                self.pkg("list [email protected] [email protected] [email protected]", exit=3)
                self.pkg("uninstall '*'")

                # Test to see if solver will be happy w/ renamed packages,
                # already installed dependencies.
                self.pkg("exact-install pkg:/[email protected] pkg_need_rename "
                    "[email protected]")
                self.pkg("verify")
                self.pkg("list [email protected] [email protected]")
                self.pkg("uninstall '*'")

                # test to see if solver will install new verion of existing
                # package rather than add new package
                self.pkg("exact-install [email protected] [email protected]")  # install pkg
                self.pkg("verify")
                self.pkg("list [email protected] [email protected]")
                self.pkg("list [email protected]", exit=1)
                self.pkg("exact-install pkg8 pkg9")
                self.pkg("verify")
                self.pkg("list [email protected] [email protected]")
                self.pkg("list [email protected]", exit=1)
                self.pkg("uninstall '*'")

        def test_origin_dependencies(self):
                """Get origin dependencies working"""

                self.origin_dependencies_helper("install")
                self.origin_dependencies_helper("exact-install")

        def origin_dependencies_helper(self, install_cmd):
                self.set_image(0)
                self.image_create(self.rurl)
                self.set_image(1)
                self.image_create(self.rurl)
                self.set_image(0)
                # check install or exact-install behavior
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("%s pkg10" % install_cmd)
                self.pkg("list [email protected]")
                self.pkg("%s pkg10" % install_cmd)
                self.pkg("list [email protected]")
                self.pkg("uninstall '*'")
                # check image-update behavior
                self.pkg("install [email protected]")
                self.pkg("image-update")
                self.pkg("list [email protected]")
                self.pkg("image-update")
                self.pkg("list [email protected]")
                self.pkg("uninstall '*'")
                # check that dependencies are ignored if
                # dependency not present
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("uninstall '*'")
                # make sure attempts to force install don't work
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("%s [email protected]" % install_cmd, exit=1)
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("uninstall '*'")
                # check origin root-image=true dependencies
                # relies on SUNWcs in root image; make image 1 the root image
                self.set_image(1)
                self.pkg("%s [email protected]" % install_cmd)
                self.set_image(0)
                live_root = self.img_path(1)
                self.pkg("-D simulate_live_root=%s %s [email protected]" % \
                    (live_root, install_cmd))
                self.pkg("-D simulate_live_root=%s %s [email protected]" %
                    (live_root, install_cmd), exit=1)
                self.pkg("uninstall '*'")

        def test_parent_dependencies(self):
                self.parent_dependencies_helper("install")
                self.parent_dependencies_helper("exact-install")

        def parent_dependencies_helper(self, install_cmd):
                self.set_image(0)
                self.image_create(self.rurl)
                self.set_image(1)
                self.image_create(self.rurl)

                # attach c2p 1 -> 0.
                self.pkg("attach-linked -p system:img1 %s" % self.img_path(0))

                # try to install or exact-instal packages that have unmet
                # parent dependencies.
                self.pkg("%s [email protected]" % install_cmd, exit=EXIT_OOPS)
                self.pkg("%s [email protected]" % install_cmd, exit=EXIT_OOPS)
                self.pkg("%s [email protected]" % install_cmd, exit=EXIT_OOPS)

                # install or exact-install packages in parent.
                self.set_image(0)
                self.pkg("install [email protected]")
                self.set_image(1)

                # try to install or exact-install packages that have unmet
                # parent dependencies.
                self.pkg("%s [email protected]" % install_cmd, exit=EXIT_OOPS)
                self.pkg("%s [email protected]" % install_cmd, exit=EXIT_OOPS)
                self.pkg("%s [email protected]" % install_cmd, exit=EXIT_OOPS)

                # install or exact-install packages in parent.
                self.set_image(0)
                self.pkg("%s [email protected]" % install_cmd)
                self.set_image(1)

                # try to install or exact-install packages that have unmet
                # parent dependencies.
                self.pkg("%s [email protected]" % install_cmd, exit=EXIT_OOPS)
                self.pkg("%s [email protected]" % install_cmd, exit=EXIT_OOPS)
                self.pkg("%s [email protected]" % install_cmd, exit=EXIT_OOPS)

                # install packages in parent
                self.set_image(0)
                self.pkg("update [email protected]")
                self.set_image(1)

                # try to install or exact-install packages that have unmet parent dependencies.
                self.pkg("%s [email protected]" % install_cmd, exit=EXIT_OOPS)

                # try to install or exact-install packages that have satisfied parent deps.
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("verify")
                self.pkg("uninstall [email protected]")
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("verify")
                self.pkg("uninstall [email protected]")

                # install or exact-install packages in parent.
                self.set_image(0)
                if install_cmd == "install":
                        self.pkg("install [email protected]")
                else:
                        self.pkg("exact-install [email protected] [email protected]")
                self.set_image(1)

                # try to install or exact-install packages that have satisfied.
                # parent deps.
                self.pkg("%s [email protected]" % install_cmd)
                self.pkg("verify")
                self.pkg("uninstall [email protected]")


class TestMultipleDepots(pkg5unittest.ManyDepotTestCase):
        # Only start/stop the depot once (instead of for every test)
        persistent_setup = True

        foo10 = """
            open [email protected],5.11-0
            close"""

        bar10 = """
            open [email protected],5.11-0
            close"""

        moo10 = """
            open [email protected],5.11-0
            close"""

        quux10 = """
            open [email protected],5.11-0
            add depend type=optional [email protected]
            close"""

        baz10 = """
            open [email protected],5.11-0
            add depend type=require [email protected]
            close"""

        corge10 = """
            open [email protected],5.11-0
            close"""

        optional10 = """
            open [email protected],5.11-0
            close"""

        upgrade_p10 = """
            open [email protected],5.11-0
            close"""

        upgrade_p11 = """
            open [email protected],5.11-0
            close"""

        upgrade_np10 = """
            open [email protected],5.11-0
            close"""

        upgrade_np11 = """
            open [email protected],5.11-0
            close"""

        incorp_p10 = """
            open [email protected],5.11-0
            add depend type=incorporate [email protected]
            close"""

        incorp_p11 = """
            open [email protected],5.11-0
            add depend type=incorporate [email protected]
            close"""

        incorp_np10 = """
            open [email protected],5.11-0
            add depend type=incorporate [email protected]
            close"""

        incorp_np11 = """
            open [email protected],5.11-0
            add depend type=incorporate [email protected]
            close"""

        def setUp(self):
                """ depot 1 gets foo and moo, depot 2 gets foo and bar,
                    depot 3 is empty, depot 4 gets [email protected]
                    depot 5 gets corge10, depot6 is empty
                    depot7 is a copy of test1's repository for test3
                    depot1 is mapped to publisher test1 (preferred)
                    depot2 is mapped to publisher test2
                    depot3 is not mapped during setUp
                    depot4 is not mapped during setUp
                    depot5 is not mapped during setUp
                    depot6 is not mapped during setUp"""

                # Two depots are intentionally started for some publishers.
                pkg5unittest.ManyDepotTestCase.setUp(self, ["test1", "test2",
                    "test3", "test2", "test4", "test1", "test3"])

                self.rurl1 = self.dcs[1].get_repo_url()
                self.durl1 = self.dcs[1].get_depot_url()
                self.pkgsend_bulk(self.rurl1, (self.foo10, self.moo10,
                    self.quux10, self.optional10, self.upgrade_p10,
                    self.upgrade_np11, self.incorp_p10, self.incorp_p11,
                    self.incorp_np10, self.incorp_np11, self.baz10,
                    self.corge10))

                self.rurl2 = self.dcs[2].get_repo_url()
                self.durl2 = self.dcs[2].get_depot_url()
                self.pkgsend_bulk(self.rurl2, (self.foo10, self.bar10,
                    self.upgrade_p11, self.upgrade_np10, self.corge10))

                self.rurl3 = self.dcs[3].get_repo_url()

                self.rurl4 = self.dcs[4].get_repo_url()
                self.pkgsend_bulk(self.rurl4, self.upgrade_np11)

                self.rurl5 = self.dcs[5].get_repo_url()
                self.pkgsend_bulk(self.rurl5, self.corge10)

                self.rurl6 = self.dcs[6].get_repo_url()
                self.rurl7 = self.dcs[7].get_repo_url()

                # Copy contents of test1's repo to a repo for test3.
                d1dir = self.dcs[1].get_repodir()
                d2dir = self.dcs[7].get_repodir()
                self.copy_repository(d1dir, d2dir, { "test1": "test3" })
                self.dcs[7].get_repo(auto_create=True).rebuild()

                # Create image and hence primary publisher
                self.image_create(self.rurl1, prefix="test1")

                # Create second publisher using depot #2
                self.pkg("set-publisher -O " + self.rurl2 + " test2")

        def test_01_basics(self):
                self.pkg("list -a")

                # Install and uninstall moo (which is unique to depot 1)
                self.pkg("install moo")

                self.pkg("list")
                self.pkg("uninstall moo")

                # Install and uninstall bar (which is unique to depot 2)
                self.pkg("install bar")

                self.pkg("list")

                self.pkg("uninstall bar")

                # Install and uninstall foo (which is in both depots)
                # In this case, we should select foo from depot 1, since
                # it is preferred.
                self.pkg("install foo")

                self.pkg("list pkg://test1/foo")

                self.pkg("uninstall foo")

        def test_02_basics(self):
                """ Test install from an explicit preferred publisher """
                self.pkg("install pkg://test1/foo")
                self.pkg("list foo")
                self.pkg("list pkg://test1/foo")
                self.pkg("uninstall foo")

        def test_03_basics(self):
                """ Test install from an explicit non-preferred publisher """
                self.pkg("install pkg://test2/foo")
                self.pkg("list foo")
                self.pkg("list pkg://test2/foo")
                self.pkg("uninstall foo")

        def test_04_upgrade_preferred_to_non_preferred(self):
                """Install a package from the preferred publisher, and then
                upgrade it, failing to implicitly switching to a non-preferred
                publisher and then managing it explicitly"""
                self.pkg("list -a upgrade-p")
                self.pkg("install [email protected]")
                self.pkg("install [email protected]", exit=1)
                self.pkg("install pkg://test2/[email protected]")
                self.pkg("uninstall upgrade-p")

        def test_05_upgrade_non_preferred_to_preferred(self):
                """Install a package from a non-preferred publisher, and then
                try to upgrade it, failing to implicitly switch to the preferred
                publisher and then succeed doing it explicitly."""
                self.pkg("list -a upgrade-np")
                self.pkg("install [email protected]")
                self.pkg("install [email protected]", exit=1)
                self.pkg("install pkg://test1/[email protected]")
                self.pkg("uninstall upgrade-np")

        def test_06_upgrade_preferred_to_non_preferred_incorporated(self):
                """Install a package from the preferred publisher, and then
                upgrade it, failing to implicitly switch to a non-preferred
                publisher, when the package is constrained by an
                incorporation, and then succeed when doing so explicitly"""

                self.pkg("list -a upgrade-p incorp-p")
                self.pkg("install [email protected]")
                self.pkg("install upgrade-p")
                self.pkg("install [email protected]", exit=1)
                self.pkg("install [email protected] pkg://test2/[email protected]")
                self.pkg("list [email protected]")
                self.pkg("uninstall '*'")

        def test_07_upgrade_non_preferred_to_preferred_incorporated(self):
                """Install a package from the preferred publisher, and then
                upgrade it, implicitly switching to a non-preferred
                publisher, when the package is constrained by an
                incorporation."""
                self.pkg("list", exit=1)
                self.pkg("list -a upgrade-np incorp-np")
                self.pkg("install [email protected]")
                self.pkg("install upgrade-np", exit=1)
                self.pkg("uninstall '*'")

        def test_08_install_repository_access(self):
                """Verify that packages can still be installed from a repository
                even when any of the other repositories are not reachable and
                --no-refresh is used."""

                # Change the second publisher to point to an unreachable URI.
                self.pkg("set-publisher --no-refresh -O http://test.invalid7 "
                    "test2")

                # Verify that no packages are installed.
                self.pkg("list", exit=1)

                # Verify moo can not be installed (as only depot1 has it) since
                # test2 cannot be reached (and needs a refresh).
                self.pkg("install moo", exit=1)

                # Verify moo can be installed (as only depot1 has it) even
                # though test2 cannot be reached (and needs a refresh) if
                # --no-refresh is used.
                self.pkg("install --no-refresh moo")

                self.pkg("uninstall moo")

                # Reset the test2 publisher.
                self.pkg("set-publisher -O %s test2" % self.rurl2)

                # Install v1.0 of upgrade-np from test2 to prepare for
                # update.
                self.pkg("install [email protected]")

                # Set test1 to point to an unreachable URI.
                self.pkg("set-publisher --no-refresh -O http://test.invalid7 "
                    "test1")

                # Set test2 so that upgrade-np has a new version available
                # even though test1's repository is not accessible.
                self.pkg("set-publisher -O %s test2" % self.rurl4)

                # Verify update does not work since test1 is unreachable
                # even though [email protected] is available from test2.
                self.pkg("update", exit=1)

                # Verify update works even though test1 is unreachable
                # since [email protected] is available from test2 if --no-refresh
                # is used.
                self.pkg("update --no-refresh")

                # Now reset everything for the next test.
                self.pkg("uninstall upgrade-np")
                self.pkg("set-publisher --no-refresh -O %s test1" % self.rurl1)
                self.pkg("set-publisher -O %s test2" % self.rurl2)

        def test_09_uninstall_from_wrong_publisher(self):
                """Install a package from a publisher and try to remove it
                using a different publisher name; this should fail."""
                self.pkg("install foo")
                self.pkg("uninstall pkg://test2/foo", exit=1)
                # Check to make sure that uninstalling using the explicit
                # publisher works
                self.pkg("uninstall pkg://test1/foo")

        def test_10_install_after_publisher_removal(self):
                """Install a package from a publisher that has an optional
                dependency; then change the preferred publisher and remove the
                original publisher and then verify that installing the package
                again succeeds since it is essentially a no-op."""
                self.pkg("install [email protected]")
                self.pkg("set-publisher -P test2")
                self.pkg("unset-publisher test1")
                self.pkg("list -avf")

                # Attempting to install an already installed package should
                # be a no-op even if the corresponding publisher no longer
                # exists.
                self.pkg("install [email protected]", exit=4)

                # Update should work if we don't see the optional
                # dependency.
                self.pkg("update", exit=4)

                # Add back the installed package's publisher, but using a
                # a repository with an empty catalog.  After that, attempt to
                # install the package again, which should succeed even though
                # the fmri is no longer in the publisher's catalog.
                self.pkg("set-publisher -O %s test1" % self.rurl6)
                self.pkg("install [email protected]", exit=4)
                self.pkg("info [email protected]")
                self.pkg("unset-publisher test1")

                # Add a new publisher, with the same packages as the installed
                # publisher.  Then, add back the installed package's publisher,
                # but using an empty repository.  After that, attempt to install
                # the package again, which should succeed since at least one
                # publisher has the package in its catalog.
                self.pkg("set-publisher -O %s test3" % self.rurl7)
                self.pkg("set-publisher -O %s test1" % self.rurl6)
                self.pkg("info -r pkg://test3/[email protected]")
                self.pkg("install [email protected]", exit=4)
                self.pkg("unset-publisher test1")
                self.pkg("unset-publisher test3")

                self.pkg("set-publisher -O %s test1" % self.rurl1)
                self.pkg("info -r pkg://test1/[email protected]")
                self.pkg("unset-publisher test1")

                # Add a new publisher, using the installed package publisher's
                # repository.  After that, attempt to install the package again,
                # which should simply result in a 'no updates necessary' exit
                # code since the removed publisher's package is already the
                # newest version available.
                #
                self.pkg("set-publisher -O %s test3" % self.rurl7)
                self.pkg("install [email protected]", exit=4)
                self.pkg("unset-publisher test3")

                # Change the image metadata back to where it was, in preparation
                # for subsequent tests.
                self.pkg("set-publisher -O %s -P test1" % self.rurl1)

                # Remove the installed packages.
                self.pkg("uninstall quux")

        def test_11_uninstall_after_preferred_publisher_change(self):
                """Install a package from the preferred publisher, change the
                preferred publisher, and attempt to remove the package."""
                self.pkg("install [email protected]")
                self.pkg("set-publisher -P test2")
                self.pkg("uninstall foo")
                # Change the image metadata back to where it was, in preparation
                # for the next test.
                self.pkg("set-publisher -P test1")

        def test_12_uninstall_after_publisher_removal(self):
                """Install a package from the preferred publisher, remove the
                preferred publisher, and then evaluate whether an uninstall
                would succeed regardless of whether its publisher still exists
                or another publisher has the same fmri in its catalog."""
                self.pkg("install [email protected]")
                self.pkg("set-publisher -P test2")
                self.pkg("unset-publisher test1")

                # Attempting to uninstall should work even if the corresponding
                # publisher no longer exists.
                self.pkg("uninstall -nv foo")

                # Add back the installed package's publisher, but using a
                # a repository with an empty catalog.  After that, attempt to
                # uninstall the package again, which should succeed even though
                # the fmri is no longer in the publisher's catalog.
                self.pkg("set-publisher -O %s test1" % self.rurl6)
                self.pkg("uninstall -nv foo")
                self.pkg("unset-publisher test1")

                # Add a new publisher, with a repository with the same packages
                # as the installed publisher.  Then, add back the installed
                # package's publisher using an empty repository.  After that,
                # attempt to uninstall the package again, which should succeed
                # even though the package's installed publisher is known, but
                # doesn't have the package's fmri in its catalog, but the
                # package's fmri is in a different publisher's catalog.
                self.pkg("set-publisher -O %s test3" % self.rurl7)
                self.pkg("set-publisher -O %s test1" % self.rurl6)
                self.pkg("uninstall -nv foo")
                self.pkg("unset-publisher test1")
                self.pkg("unset-publisher test3")

                # Add a new publisher, with a repository with the same packages
                # as the installed publisher.  After that, attempt to uninstall
                # the package again, which should succeed even though the fmri
                # is only in a different publisher's catalog.
                self.pkg("set-publisher -O %s test3" % self.rurl7)
                self.pkg("uninstall -nv foo")
                self.pkg("unset-publisher test3")

                # Finally, actually remove the package.
                self.pkg("uninstall -v foo")

                # Change the image metadata back to where it was, in preparation
                # for subsequent tests.
                self.pkg("set-publisher -O %s -P test1" % self.rurl1)

        def test_13_non_preferred_multimatch(self):
                """Verify that when multiple non-preferred publishers offer the
                same package that the expected install behaviour occurs."""

                self.pkg("set-publisher -P -O %s test3" % self.rurl3)

                # make sure we look here first; tests rely on that
                self.pkg("set-publisher --search-before=test2 test1")
                self.pkg("publisher")
                # First, verify that installing a package from a non-preferred
                # publisher will cause its dependencies to be installed from the
                # same publisher if the preferred publisher does not offer them.
                self.pkg("list -a")
                self.pkg("install pkg://test1/baz")
                self.pkg("list")
                self.pkg("info baz | grep test1")
                self.pkg("info corge | grep test1")
                self.pkg("uninstall baz corge")

                # Next, verify that naming the specific publishers for a package
                # and all of its dependencies will install the package from the
                # specified sources instead of the same publisher the package is a
                # dependency of.
                self.pkg("install pkg://test1/baz pkg://test2/corge")
                self.pkg("info baz | grep test1")
                self.pkg("info corge | grep test2")
                self.pkg("uninstall baz corge")

                # Finally, cleanup for the next test.
                self.pkg("set-publisher -P test1")
                self.pkg("unset-publisher test3")

        def test_14_nonsticky_publisher(self):
                """Test various aspects of the stick/non-sticky
                behavior of publishers"""

                # For ease of debugging
                self.pkg("list -a")
                # install from non-preferred repo explicitly
                self.pkg("install pkg://test2/[email protected]")
                # Demonstrate that perferred publisher is not
                # acceptable, since test2 is sticky by default
                self.pkg("install [email protected]", exit=1) # not right repo
                # Check that we can proceed once test2 is not sticky
                self.pkg("set-publisher --non-sticky test2")
                self.pkg("install [email protected]") # should work now
                # Restore to pristine
                self.pkg("set-publisher --sticky test2")
                self.pkg("uninstall upgrade-np")
                # Repeat the test w/ preferred
                self.pkg("install upgrade-p")
                self.pkg("set-publisher -P test2")
                self.pkg("install [email protected]", exit=1) #orig pub is sticky
                self.pkg("set-publisher --non-sticky test1")  #not anymore
                self.pkg("install [email protected]")
                self.pkg("set-publisher -P --sticky test1") # restore
                self.pkg("uninstall '*'")
                # Check  that search order can be overridden w/ explicit
                # version specification...
                self.pkg("install upgrade-p")
                self.pkg("install [email protected]", exit=1)
                self.pkg("set-publisher --non-sticky test1")
                self.pkg("install [email protected]") # find match later on
                self.pkg("set-publisher --sticky test1")
                self.pkg("uninstall '*'")

        def test_15_nonsticky_update(self):
                """Test to make sure update follows the same
                publisher selection mechanisms as pkg install"""

                # try update
                self.pkg("install pkg://test2/[email protected]")
                self.pkg("update", exit=4)
                self.pkg("list [email protected]")
                self.pkg("set-publisher --non-sticky test2")
                self.pkg("publisher")
                self.pkg("list -a upgrade-np")
                self.pkg("update '*@*'")
                self.pkg("list [email protected]")
                self.pkg("set-publisher --sticky test2")
                self.pkg("uninstall '*'")

        def test_16_disabled_nonsticky(self):
                """Test to make sure disabled publishers are
                automatically made non-sticky, and after
                being enabled keep their previous value
                of stickiness"""

                # For ease of debugging
                self.pkg("list -a")
                # install from non-preferred repo explicitly
                self.pkg("install pkg://test2/[email protected]")
                # Demonstrate that perferred publisher is not
                # acceptable, since test2 is sticky by default
                self.pkg("install [email protected]", exit=1) # not right repo
                # Disable test2 and then we should be able to proceed
                self.pkg("set-publisher --disable test2")
                self.pkg("install [email protected]")
                self.pkg("publisher")
                self.pkg("set-publisher --enable test2")
                self.pkg("publisher")
                self.pkg("publisher | egrep sticky", exit=1 )

        def test_17_dependency_is_from_deleted_publisher(self):
                """Verify that packages installed from a publisher that has
                been removed can still satisfy dependencies."""

                self.pkg("set-publisher -O %s test4" % self.rurl5)
                self.pkg("install pkg://test4/corge")
                self.pkg("set-publisher --disable test2")
                self.pkg("set-publisher --disable test4")
                self.pkg("list -af")
                self.pkg("publisher")
                # this should work, since dependency is already installed
                # even though it is from a disabled publisher
                self.pkg("install [email protected]")

        def test_18_upgrade_across_publishers(self):
                """Verify that an install/update of specific packages when
                there is a newer package version available works as expected.
                """

                # Ensure a new image is created.
                self.image_create(self.rurl1, prefix="test1", destroy=True)

                # Add second publisher using repository #2.
                self.pkg("set-publisher -O " + self.rurl2 + " test2")

                # Install older version of package from test1.
                self.pkg("install pkg://test1/[email protected]")

                # Verify update of all packages results in nothing to do even
                # after test2 is set as preferred.
                self.pkg("set-publisher -P test2")
                self.pkg("update -v", exit=4)

                # Verify setting test1 as non-sticky would result in update.
                self.pkg("set-publisher --non-sticky test1")
                self.pkg("update -n")

                # Verify update of 'upgrade-p' package will result in upgrade
                # from 1.0 -> 1.1.
                self.pkg("update upgrade-p")
                self.pkg("info pkg://test2/[email protected]")

                # Revert to 1.0 and verify install behaves the same.
                self.pkg("update pkg://test1/[email protected]")
                self.pkg("install upgrade-p")
                self.pkg("info pkg://test2/[email protected]")

        def test_19_refresh_failure(self):
                """Test that pkg client returns with exit code 1 when only one
                publisher is specified and it's not reachable (bug 7176158)."""

                # Create private image for this test.
                self.image_create(self.rurl1, prefix="test1")
                # Set origin to an invalid repo.
                self.pkg("set-publisher --no-refresh -O http://test.invalid7 "
                    "test1")

                # Check if install -n returns with exit code 1
                self.pkg("install -n moo", exit=1)


class TestImageCreateCorruptImage(pkg5unittest.SingleDepotTestCaseCorruptImage):
        """
        If a new essential directory is added to the format of an image it will
        be necessary to update this test suite. To update this test suite,
        decide in what ways it is necessary to corrupt the image (removing the
        new directory or file, or removing the some or all of contents of the
        new directory for example). Make the necessary changes in
        pkg5unittest.SingleDepotTestCaseCorruptImage to allow the needed
        corruptions, then add new tests to the suite below. Be sure to add
        tests for both Full and User images, and perhaps Partial images if
        situations are found where these behave differently than Full or User
        images.
        """

        # Only start/stop the depot once (instead of for every test)
        persistent_setup = True

        foo11 = """
            open [email protected],5.11-0
            add dir mode=0755 owner=root group=bin path=/lib
            add file tmp/libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.so.1
            close """

        misc_files = [ "tmp/libc.so.1" ]

        PREFIX = "unset PKG_IMAGE; cd %s;"

        def setUp(self):
                pkg5unittest.SingleDepotTestCaseCorruptImage.setUp(self)
                self.make_misc_files(self.misc_files)

        def pkg(self, command, exit=0, comment="", use_img_root=True):
                pkg5unittest.SingleDepotTestCaseCorruptImage.pkg(self, command,
                    exit=exit, comment=comment, prefix=self.PREFIX % self.dir,
                    use_img_root=use_img_root)

        # For each test:
        # A good image is created at $basedir/image
        # A corrupted image is created at $basedir/image/bad (called bad_dir
        #     in subsequent notes) in verious ways
        # The $basedir/image/bad/final directory is created and PKG_IMAGE
        #     is set to that dirctory.

        # Tests simulating a corrupted Full Image

        def test_empty_var_pkg(self):
                """ Creates an empty bad_dir. """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl,
                    set(["publisher", "cfg_cache", "file", "pkg", "index"]),
                    ["var/pkg"])

                self.pkg("install [email protected]")

        def test_var_pkg_missing_publisher(self):
                """ Creates bad_dir with only the publisher and known/state
                dir missing. """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl,
                    set(["publisher_absent", "known_absent"]),
                    ["var/pkg"])

                self.pkg("install [email protected]")

        def test_var_pkg_missing_cfg_cache(self):
                """ Creates bad_dir with only the cfg_cache file missing. """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl,
                    set(["cfg_cache_absent"]), ["var/pkg"])

                self.pkg("-D simulate_live_root=%s install [email protected]" %
                    self.backup_img_path(), use_img_root=False)

        def test_var_pkg_missing_index(self):
                """ Creates bad_dir with only the index dir missing. """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl, set(
                    ["index_absent"]), ["var/pkg"])

                self.pkg("install [email protected]")

        def test_var_pkg_missing_publisher_empty(self):
                """ Creates bad_dir with all dirs and files present, but
                with an empty publisher and state/known dir.
                """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl,
                    set(["publisher_empty", "known_empty"]), ["var/pkg"])

                # This is expected to fail because it will see an empty
                # publisher directory and not rebuild the files as needed
                self.pkg("install --no-refresh [email protected]", exit=1)
                self.pkg("install [email protected]")

        def test_var_pkg_missing_publisher_empty_hit_then_refreshed_then_hit(
            self):
                """ Creates bad_dir with all dirs and files present, but with an
                with an empty publisher and state/known dir. This is to ensure
                that refresh will work, and that an install after the refresh
                also works.
                """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl,
                    set(["publisher_empty", "known_empty"]), ["var/pkg"])

                self.pkg("install --no-refresh [email protected]", exit=1)
                self.pkg("refresh")
                self.pkg("install [email protected]")


        def test_var_pkg_left_alone(self):
                """ Sanity check to ensure that the code for creating
                a bad_dir creates a good copy other than what's specified
                to be wrong. """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl, set(), ["var/pkg"])

                self.pkg("install [email protected]")

        # Tests simulating a corrupted User Image

        # These tests are duplicates of those above but instead of creating
        # a corrupt full image, they create a corrupt User image.

        def test_empty_ospkg(self):
                """ Creates a corrupted image at bad_dir by creating empty
                bad_dir.  """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl,
                    set(["publisher", "cfg_cache", "file", "pkg", "index"]),
                    [".org.opensolaris,pkg"])

                self.pkg("install [email protected]")

        def test_ospkg_missing_publisher(self):
                """ Creates a corrupted image at bad_dir by creating bad_dir
                with only the publisher and known/state dir missing. """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl,
                    set(["publisher_absent", "known_absent"]),
                        [".org.opensolaris,pkg"])

                self.pkg("install [email protected]")

        def test_ospkg_missing_cfg_cache(self):
                """ Creates a corrupted image at bad_dir by creating
                bad_dir with only the cfg_cache file missing.  """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl,
                    set(["cfg_cache_absent"]), [".org.opensolaris,pkg"])

                self.pkg("-D simulate_live_root=%s install [email protected]" %
                    self.backup_img_path(), use_img_root=False)

        def test_ospkg_missing_index(self):
                """ Creates a corrupted image at bad_dir by creating
                bad_dir with only the index dir missing. """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl, set(["index_absent"]),
                    [".org.opensolaris,pkg"])

                self.pkg("install [email protected]")

        def test_ospkg_missing_publisher_empty(self):
                """ Creates a corrupted image at bad_dir by creating bad_dir
                with all dirs and files present, but with an empty publisher
                and known/state dir. """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl,
                    set(["publisher_empty", "known_empty"]),
                    [".org.opensolaris,pkg"])

                self.pkg("install --no-refresh [email protected]", exit=1)

        def test_ospkg_missing_publisher_empty_hit_then_refreshed_then_hit(self):
                """ Creates bad_dir with all dirs and files present, but with
                an empty publisher and known/state dir. This is to ensure that
                refresh will work, and that an install after the refresh also
                works.
                """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl,
                    set(["publisher_empty", "known_empty"]),
                    [".org.opensolaris,pkg"])

                self.pkg("install --no-refresh [email protected]", exit=1)
                self.pkg("refresh")
                self.pkg("install [email protected]")

        def test_ospkg_left_alone(self):
                """ Sanity check to ensure that the code for creating
                a bad_dir creates a good copy other than what's specified
                to be wrong. """

                self.pkgsend_bulk(self.rurl, self.foo11)
                self.image_create(self.rurl)

                self.dir = self.corrupt_image_create(self.rurl, set(),
                    [".org.opensolaris,pkg"])

                self.pkg("install [email protected]")


class TestPkgInstallObsolete(pkg5unittest.SingleDepotTestCase):
        """Test cases for obsolete packages."""

        persistent_setup = True
        def test_basic_install(self):
                foo1 = """
                    open foo@1
                    add dir path=usr mode=0755 owner=root group=root
                    close
                """
                # Obsolete packages can have metadata
                foo2 = """
                    open foo@2
                    add set name=pkg.obsolete value=true
                    add set name=pkg.summary value="A test package"
                    close
                """

                fbar = """
                    open fbar@1
                    add depend type=require fmri=foo@2
                    close
                """

                qbar = """
                    open qbar@1
                    add depend type=require fmri=qux@2
                    close
                """

                qux1 = """
                    open qux@1
                    add dir path=usr mode=0755 owner=root group=root
                    close
                """

                qux2 = """
                    open qux@2
                    add set name=pkg.renamed value=true
                    add depend type=require fmri=foo@1
                    close
                """

                fred1 = """
                    open fred@1
                    add depend type=require fmri=foo
                    close
                """
                fred2 = """
                    open fred@2
                    close
                """

                self.pkgsend_bulk(self.rurl, (foo1, foo2, fbar, qbar, qux1,
                    qux2, fred1))

                self.image_create(self.rurl)

                # First install the non-obsolete version of foo
                self.pkg("install foo@1")
                self.pkg("list foo@1")

                # Now install the obsolete version, and ensure it disappears (5)
                self.pkg("install foo")
                self.pkg("list foo", exit=1)

                # Explicitly installing an obsolete package succeeds, but
                # results in nothing on the system. (1)
                self.pkg("install foo@2", exit=4)
                self.pkg("list foo", exit=1)

                # Installing a package with a dependency on an obsolete package
                # fails. (2)
                self.pkg("install fbar", exit=1)

                # Installing a package with a dependency on a renamed package
                # succeeds, leaving the first package and the renamed package on
                # the system, as well as the empty, pre-renamed package. (3)
                self.pkg("install qbar")
                self.pkg("list qbar")
                self.pkg("list foo@1")
                self.pkg("list qux | grep -- i-r")
                self.pkg("uninstall '*'") #clean up for next test
                # A simple rename test: First install the pre-renamed version of
                # qux.  Then install the renamed version, and see that the new
                # package is installed, and the renamed package is installed,
                # but marked renamed.  (4)
                self.pkg("install qux@1")
                self.pkg("install qux") # upgrades qux
                self.pkg("list foo@1")
                self.pkg("list qux", exit=1)
                self.pkg("uninstall '*'") #clean up for next test

                # Install a package that's going to be obsoleted and a package
                # that depends on it.  Update the package to its obsolete
                # version and see that it fails.  (6, sorta)
                self.pkg("install foo@1 fred@1")
                self.pkg("install foo@2", exit=1)
                # now add a version of fred that doesn't require foo, and
                # show that update works
                self.pkgsend_bulk(self.rurl, fred2)
                self.pkg("refresh")
                self.pkg("install foo@2")
                self.pkg("uninstall '*'") #clean up for next test
                # test fix for bug 12898
                self.pkg("install qux@1")
                self.pkg("install fred@2")
                self.pkg("list foo@1", exit=1) # should not be installed
                self.pkg("install qux") #update
                self.pkg("list foo@1")
                self.pkgrepo("remove -s %s fred@2" % self.rurl)

        def test_basic_exact_install(self):
                foo1 = """
                    open foo@1
                    add dir path=usr mode=0755 owner=root group=root
                    close
                """
                # Obsolete packages can have metadata
                foo2 = """
                    open foo@2
                    add set name=pkg.obsolete value=true
                    add set name=pkg.summary value="A test package"
                    close
                """

                fbar = """
                    open fbar@1
                    add depend type=require fmri=foo@2
                    close
                """

                qbar = """
                    open qbar@1
                    add depend type=require fmri=qux@2
                    close
                """

                qux1 = """
                    open qux@1
                    add dir path=usr mode=0755 owner=root group=root
                    close
                """

                qux2 = """
                    open qux@2
                    add set name=pkg.renamed value=true
                    add depend type=require fmri=foo@1
                    close
                """

                fred1 = """
                    open fred@1
                    add depend type=require fmri=foo
                    close
                """
                fred2 = """
                    open fred@2
                    close
                """

                self.pkgsend_bulk(self.rurl, (foo1, foo2, fbar, qbar, qux1,
                    qux2, fred1))

                self.image_create(self.rurl)

                # First exact-install the non-obsolete version of foo
                self.pkg("exact-install foo@1")
                self.pkg("list foo@1")

                # Now exact-install the obsolete version, and ensure it disappears
                # (5).
                self.pkg("exact-install foo")
                self.pkg("list foo", exit=1)

                # Explicitly exact-installing an obsolete package succeeds, but
                # results in nothing on the system. (1)
                self.pkg("exact-install foo@2", exit=4)
                self.pkg("list foo", exit=1)

                # Exact-nstalling a package with a dependency on an obsolete package
                # fails. (2)
                self.pkg("exact-install fbar", exit=1)

                # Exact-installing a package with a dependency on a renamed
                # package succeeds, leaving the first package and the renamed
                # package on the system, as well as the empty, pre-renamed
                # package. (3)
                self.pkg("exact-install qbar")
                self.pkg("list qbar")
                self.pkg("list foo@1")
                self.pkg("list qux | grep -- i-r")
                self.pkg("uninstall '*'") #clean up for next test
                # A simple rename test: First exact-install the pre-renamed
                # version of qux.  Then install the renamed version, and see
                # that the new package is installed, and the renamed package
                # is installed, but marked renamed.  (4)
                self.pkg("exact-install qux@1")
                self.pkg("exact-install qux") # upgrades qux
                self.pkg("list foo@1")
                self.pkg("list qux", exit=1)
                self.pkg("uninstall '*'") #clean up for next test

                # Exact-install a package that's going to be obsoleted and a
                # package that depends on it.  Update the package to its
                # obsolete version and see that it fails.  (6, sorta)
                self.pkg("exact-install foo@1 fred@1")
                self.pkg("exact-install foo@2 fred@1", exit=1)

                # If fred is not in the command line, we should ignore its
                # restriction and install foo@2.
                self.pkg("exact-install foo@2")
                # now add a version of fred that doesn't require foo, and
                # show that update works
                self.pkgsend_bulk(self.rurl, fred2)
                self.pkg("refresh")
                self.pkg("exact-install foo@2 fred")
                self.pkg("uninstall '*'") #clean up for next test
                self.pkgrepo("remove -s %s fred@2" % self.rurl)

        def test_basic_7a(self):
                """Upgrade a package to a version with a dependency on a renamed
                package.  A => A' (-> Br (-> C))"""

                t7ap1_1 = """
                    open t7ap1@1
                    close
                """

                t7ap1_2 = """
                    open t7ap1@2
                    add depend type=require fmri=t7ap2
                    close
                """

                t7ap2_1 = """
                    open t7ap2@1
                    add set name=pkg.renamed value=true
                    add depend type=require fmri=t7ap3
                    close
                """

                t7ap3_1 = """
                    open t7ap3@1
                    close
                """

                self.pkgsend_bulk(self.rurl, t7ap1_1)
                self.image_create(self.rurl)

                self.pkg("install t7ap1")

                self.pkgsend_bulk(self.rurl, (t7ap1_2, t7ap2_1, t7ap3_1))

                self.pkg("refresh")
                self.pkg("update")
                self.pkg("list -af")
                self.pkg("list t7ap2 | grep -- i-r")
                self.pkg("list t7ap3")

        def test_basic_7b(self):
                """Upgrade a package to a version with a dependency on a renamed
                package.  Like 7a except package A starts off depending on B.

                A (-> B) => A' (-> Br (-> C))"""

                t7bp1_1 = """
                    open t7bp1@1
                    add depend type=require fmri=t7bp2
                    close
                """

                t7bp1_2 = """
                    open t7bp1@2
                    add depend type=require fmri=t7bp2
                    close
                """

                t7bp2_1 = """
                    open t7bp2@1
                    close
                """

                t7bp2_2 = """
                    open t7bp2@1
                    add set name=pkg.renamed value=true
                    add depend type=require fmri=t7bp3
                    close
                """

                t7bp3_1 = """
                    open t7bp3@1
                    close
                """

                self.pkgsend_bulk(self.rurl, (t7bp1_1, t7bp2_1))
                self.image_create(self.rurl)

                self.pkg("install t7bp1")

                self.pkgsend_bulk(self.rurl, (t7bp1_2, t7bp2_2, t7bp3_1))

                self.pkg("refresh")
                self.pkg("update")
                self.pkg("list t7bp2 | grep -- i-r")
                self.pkg("list t7bp3")

        def test_basic_7c(self):
                """Upgrade a package to a version with a dependency on a renamed
                package.  Like 7b, except package A doesn't change.

                A (-> B) => A (-> Br (-> C))"""

                t7cp1_1 = """
                    open t7cp1@1
                    add depend type=require fmri=t7cp2
                    close
                """

                t7cp2_1 = """
                    open t7cp2@1
                    close
                """

                t7cp2_2 = """
                    open t7cp2@2
                    add set name=pkg.renamed value=true
                    add depend type=require fmri=t7cp3
                    close
                """

                t7cp3_1 = """
                    open t7cp3@1
                    close
                """

                self.pkgsend_bulk(self.rurl, (t7cp1_1, t7cp2_1))
                self.image_create(self.rurl)

                self.pkg("install t7cp1")

                self.pkgsend_bulk(self.rurl, (t7cp2_2, t7cp3_1))

                self.pkg("refresh")
                self.pkg("update")

                self.pkg("list t7cp2 | grep -- i-r")
                self.pkg("list t7cp3")

        def test_basic_6a(self):
                """Upgrade a package to a version with a dependency on an
                obsolete package.  This version is unlikely to happen in real
                life."""

                t6ap1_1 = """
                    open t6ap1@1
                    close
                """

                t6ap1_2 = """
                    open t6ap1@2
                    add depend type=require fmri=t6ap2
                    close
                """

                t6ap2_1 = """
                    open t6ap2@1
                    add set name=pkg.obsolete value=true
                    close
                """


                self.pkgsend_bulk(self.rurl, t6ap1_1)
                self.image_create(self.rurl)

                self.pkg("install t6ap1")

                self.pkgsend_bulk(self.rurl, (t6ap1_2, t6ap2_1))

                self.pkg("refresh")
                self.pkg("update", exit=4) # does nothing
                self.pkg("list t6ap1")

        def test_basic_6b(self):
                """Install a package with a dependency, and update after
                publishing updated packages for both, but where the dependency
                has become obsolete."""

                t6ap1_1 = """
                    open t6ap1@1
                    add depend type=require fmri=t6ap2
                    close
                """

                t6ap1_2 = """
                    open t6ap1@2
                    add depend type=require fmri=t6ap2
                    close
                """

                t6ap2_1 = """
                    open t6ap2@1
                    close
                """

                t6ap2_2 = """
                    open t6ap2@2
                    add set name=pkg.obsolete value=true
                    close
                """

                self.pkgsend_bulk(self.rurl, (t6ap1_1, t6ap2_1))
                self.image_create(self.rurl)

                self.pkg("install t6ap1")
                self.pkg("list")

                self.pkgsend_bulk(self.rurl, (t6ap1_2, t6ap2_2))

                self.pkg("refresh")
                self.pkg("update")
                self.pkg("list t6ap1@2 t6ap2@1")

        def test_basic_8a(self):
                """Upgrade a package to an obsolete leaf version when another
                depends on it."""

                t8ap1_1 = """
                    open t8ap1@1
                    close
                """

                t8ap1_2 = """
                    open t8ap1@2
                    add set name=pkg.obsolete value=true
                    close
                """

                t8ap2_1 = """
                    open t8ap2@1
                    add depend type=require fmri=t8ap1
                    close
                """

                self.pkgsend_bulk(self.rurl, (t8ap1_1, t8ap2_1))
                self.image_create(self.rurl)

                self.pkg("install t8ap2")

                self.pkgsend_bulk(self.rurl, t8ap1_2)

                self.pkg("refresh")
                self.pkg("update", exit=4) # does nothing
                self.pkg("list  t8ap2@1")

        def test_basic_13a(self):
                """Publish an package with a dependency, then publish both as
                obsolete, update, and see that both packages have gotten
                removed."""

                t13ap1_1 = """
                    open t13ap1@1
                    add depend type=require fmri=t13ap2
                    close
                """

                t13ap1_2 = """
                    open t13ap1@2
                    add set name=pkg.obsolete value=true
                    close
                """

                t13ap2_1 = """
                    open t13ap2@1
                    close
                """

                t13ap2_2 = """
                    open t13ap2@2
                    add set name=pkg.obsolete value=true
                    close
                """

                self.pkgsend_bulk(self.rurl, (t13ap1_1, t13ap2_1))
                self.image_create(self.rurl)

                self.pkg("install t13ap1")

                self.pkgsend_bulk(self.rurl, (t13ap1_2, t13ap2_2))

                self.pkg("refresh")
                self.pkg("update")
                self.pkg("list", exit=1)

        def test_basic_11(self):
                """Install or exact-install a package with an ambiguous name,
                where only one match is non-obsolete."""

                self.basic_11_helper("install")
                self.basic_11_helper("exact-install")

        def basic_11_helper(self, install_cmd):
                t11p1 = """
                    open netbeans@1
                    add set name=pkg.obsolete value=true
                    close
                """

                t11p2 = """
                    open developer/netbeans@1
                    close
                """

                self.pkgsend_bulk(self.rurl, (t11p1, t11p2))
                self.image_create(self.rurl)

                self.pkg("%s netbeans" % install_cmd)
                self.pkg("list pkg:/developer/netbeans")
                self.pkg("list pkg:/netbeans", exit=1)

        def test_basic_11a(self):
                """Install or exact-install a package using an ambiguous name
                where pkg is renamed to another package, but not the
                conflicting one"""

                self.basic_11a_helper("install")
                self.basic_11a_helper("exact-install")

        def basic_11a_helper(self, install_cmd):
                t11p1 = """
                    open netbonze@1
                    add set name=pkg.renamed value=true
                    add depend type=require fmri=SUNWnetbonze
                    close
                """

                t11p2 = """
                    open developer/netbonze@1
                    close
                """

                t11p3 = """
                    open SUNWnetbonze@1
                    close
                """

                self.pkgsend_bulk(self.rurl, (t11p1, t11p2, t11p3))
                self.image_create(self.rurl)

                self.pkg("%s netbonze" % install_cmd, exit=1)

        def test_basic_11b(self):
                """Install or exact-install a package using an ambiguous name
                where only one match is non-renamed, and the renamed match
                is renamed to the other."""

                self.basic_11b_helper("install")
                self.basic_11b_helper("exact-install")

        def basic_11b_helper(self, install_cmd):
                t11p1 = """
                    open netbooze@1
                    close
                """

                t11p2 = """
                    open netbooze@2
                    add set name=pkg.renamed value=true
                    add depend type=require fmri=developer/netbooze
                    close
                """

                t11p3 = """
                    open developer/netbooze@2
                    close
                """

                t11p4 = """
                    open developer/netbooze@3
                    add depend type=require fmri=developer/missing
                    close
                """

                self.pkgsend_bulk(self.rurl, (t11p1, t11p2, t11p3, t11p4))
                self.image_create(self.rurl)

                self.pkg("%s netbooze" % install_cmd)
                self.pkg("list pkg:/developer/netbooze")
                self.pkg("list pkg:/netbooze", exit=1)


        def test_basic_12(self):
                """Upgrade a package across a rename to an ambiguous name."""

                t12p1_1 = """
                    open netbeenz@1
                    close
                """

                t12p1_2 = """
                    open netbeenz@2
                    add set name=pkg.renamed value=true
                    add depend type=require fmri=pkg:/developer/netbeenz
                    close
                """

                t12p2_1 = """
                    open developer/netbeenz@1
                    close
                """

                self.pkgsend_bulk(self.rurl, t12p1_1)
                self.image_create(self.rurl)

                self.pkg("install netbeenz")

                self.pkgsend_bulk(self.rurl, (t12p1_2, t12p2_1))

                self.pkg("refresh")
                self.pkg("update -v")
                self.pkg("list pkg:/developer/netbeenz | grep -- i--")
                self.pkg("list pkg:/netbeenz", exit=1)

        def test_remove_renamed(self):
                """If a renamed package has nothing depending on it, it should
                be removed."""

                p1_1 = """
                    open remrenA@1
                    close
                """

                p1_2 = """
                    open remrenA@2
                    add set name=pkg.renamed value=true
                    add depend type=require fmri=pkg:/remrenB
                    close
                """

                p2_1 = """
                    open remrenB@1
                    close
                """

                p3_1 = """
                    open remrenC@1
                    add depend type=require fmri=pkg:/remrenA
                    close
                """

                self.pkgsend_bulk(self.rurl, p1_1)
                self.image_create(self.rurl)

                self.pkg("install remrenA")

                self.pkgsend_bulk(self.rurl, (p1_2, p2_1, p3_1))

                self.pkg("refresh")
                self.pkg("update")
                self.pkg("list remrenA", exit=1)

                # But if there is something depending on the renamed package, it
                # can't be removed.
                self.pkg("uninstall remrenB")

                self.pkg("install remrenA@1 remrenC")
                self.pkg("update")
                self.pkg("list remrenA")

        def test_chained_renames(self):
                """If there are multiple renames, make sure we delete as much
                as possible, but no more."""

                A1 = """
                    open chained_A@1
                    close
                """

                A2 = """
                    open chained_A@2
                    add set name=pkg.renamed value=true
                    add depend type=require fmri=pkg:/chained_B@2
                    close
                """

                B2 = """
                    open chained_B@2
                    add set name=pkg.renamed value=true
                    add depend type=require fmri=pkg:/chained_C@2
                    close
                """

                C2 = """
                    open chained_C@2
                    close
                """

                X = """
                    open chained_X@1
                    add depend type=require fmri=pkg:/chained_A
                    close
                """

                Y = """
                    open chained_Y@1
                    add depend type=require fmri=pkg:/chained_B
                    close
                """

                Z = """
                    open chained_Z@1
                    close
                """

                self.pkgsend_bulk(self.rurl, (A1, A2, B2, C2, X, Y, Z))

                self.image_create(self.rurl)

                self.pkg("install chained_A@1 chained_X chained_Z")
                for p in ["chained_A@1", "chained_X@1"]:
                        self.pkg("list %s" % p)
                self.pkg("update")

                for p in ["chained_A@2", "chained_X@1", "chained_B@2",
                    "chained_C@2", "chained_Z"]:
                        self.pkg("list %s" % p)

                self.pkg("uninstall chained_X")

                for p in ["chained_C@2", "chained_Z"]:
                        self.pkg("list %s" % p)

                # make sure renamed pkgs no longer needed are uninstalled
                for p in ["chained_A@2", "chained_B@2"]:
                        self.pkg("list %s" % p, exit=1)

        def test_unobsoleted(self):
                """Ensure that the existence of an obsolete package version
                does not prevent the system from upgrading to or installing
                a resurrected version."""

                self.unobsoleted_helper("install")
                self.unobsoleted_helper("exact-install")

        def unobsoleted_helper(self, install_cmd):
                pA_1 = """
                    open reintroA@1
                    close
                """

                pA_2 = """
                    open reintroA@2
                    add set name=pkg.obsolete value=true
                    close
                """

                pA_3 = """
                    open reintroA@3
                    close
                """

                pB_1 = """
                    open reintroB@1
                    add depend type=require fmri=pkg:/reintroA@1
                    close
                """

                pB_2 = """
                    open reintroB@2
                    close
                """

                pB_3 = """
                    open reintroB@3
                    add depend type=require fmri=pkg:/reintroA@3
                    close
                """

                self.pkgsend_bulk(self.rurl, (pA_1, pA_2, pA_3, pB_1, pB_2,
                    pB_3))
                self.image_create(self.rurl)

                # Check installation of an unobsoleted package with no
                # dependencies.

                # Testing reintroA@1 -> reintroA@3 with update
                self.pkg("install reintroA@1")
                self.pkg("refresh")
                self.pkg("update")
                self.pkg("list reintroA@3")
                self.pkg("uninstall reintroA")

                # Testing reintroA@1 -> reintroA@3 with install or
                # exact-install.
                self.pkg("%s reintroA@1" % install_cmd)
                self.pkg("%s reintroA@3" % install_cmd)
                self.pkg("list reintroA@3")
                self.pkg("uninstall reintroA")

                # Testing empty image -> reintroA@3 with install or
                # exact-install.
                self.pkg("%s reintroA@3" % install_cmd)
                self.pkg("list reintroA@3")
                self.pkg("uninstall reintroA")

                # Testing reintroA@1 -> reintroA@2 -> reintroA@3 with install
                # or exact-install.
                self.pkg("%s reintroA@1" % install_cmd)
                self.pkg("%s reintroA@2" % install_cmd)
                self.pkg("%s reintroA@3" % install_cmd)
                self.pkg("list reintroA@3")
                self.pkg("uninstall reintroA")

                # Check installation of a package with an unobsoleted
                # dependency.

                # Testing reintroB@1 -> reintroB@3 with update
                self.pkg("install reintroB@1")
                self.pkg("refresh")
                self.pkg("update")
                self.pkg("list reintroB@3")
                self.pkg("list reintroA@3")
                self.pkg("uninstall reintroB reintroA")

                # Testing reintroB@1 -> reintroB@3 with install or
                # exact-install
                self.pkg("%s reintroB@1" % install_cmd)
                self.pkg("%s reintroB@3" % install_cmd)
                self.pkg("list reintroB@3")
                self.pkg("list reintroA@3")
                self.pkg("uninstall reintroB reintroA")

                # Testing empty image -> reintroB@3 with install or
                # exact-install
                self.pkg("%s reintroB@3" % install_cmd)
                self.pkg("list reintroB@3")
                self.pkg("list reintroA@3")
                self.pkg("uninstall reintroB reintroA")

                # Testing reintroB@1 -> reintroB@2 -> reintroB@3 with install
                # or exact-install
                self.pkg("%s reintroB@1" % install_cmd)
                self.pkg("%s reintroB@2" % install_cmd)
                self.pkg("%s reintroB@3" % install_cmd)
                self.pkg("list reintroB@3")
                self.pkg("list reintroA@3")
                self.pkg("uninstall reintroB reintroA")

        def test_incorp_1(self):
                """We should be able to incorporate an obsolete package."""

                self.incorp_1_helper("install")
                self.incorp_1_helper("exact-install")

        def incorp_1_helper(self, install_cmd):
                p1_1 = """
                    open inc1p1@1
                    add depend type=incorporate fmri=inc1p2@1
                    close
                """

                p2_1 = """
                    open inc1p2@1
                    add set name=pkg.obsolete value=true
                    close
                """

                self.pkgsend_bulk(self.rurl, (p1_1, p2_1))
                self.image_create(self.rurl)

                if install_cmd == "install":
                        self.pkg("install inc1p1")
                        self.pkg("install inc1p2", exit=4)

                        self.pkg("list inc1p2", exit=1)
                else:
                        self.pkg("exact-install inc1p1")

        def test_incorp_2(self):
                """We should be able to continue incorporating a package when it
                becomes obsolete on upgrade."""

                p1_1 = """
                    open inc2p1@1
                    add depend type=incorporate fmri=inc2p2@1
                    close
                """

                p1_2 = """
                    open inc2p1@2
                    add depend type=incorporate fmri=inc2p2@2
                    close
                """

                p2_1 = """
                    open inc2p2@1
                    close
                """

                p2_2 = """
                    open inc2p2@2
                    add set name=pkg.obsolete value=true
                    close
                """

                self.pkgsend_bulk(self.rurl, (p1_1, p2_1))
                self.image_create(self.rurl)

                self.pkg("install inc2p1 inc2p2")

                self.pkgsend_bulk(self.rurl, (p1_2, p2_2))

                self.pkg("refresh")
                self.pkg("list -afv")
                self.pkg("update -v")
                self.pkg("list inc2p2", exit=1)


class TestObsoletionNestedIncorporations(pkg5unittest.SingleDepotTestCase):
        # Only start/stop the depot once (instead of for every test)

        persistent_setup = True

        bug_15713570 = """
            open [email protected],5.11-0
            add depend type=require [email protected]
            close
            open [email protected],5.11-0
            add depend type=require fmri=osnet-incorporation
            close
            open [email protected],5.11-0
            add set name=pkg.obsolete value=true
            close
            open [email protected],5.11-0
            add depend type=incorporate fmri=osnet-incorporation
            add depend type=incorporate fmri=osnet-incorporation@1
            add depend type=incorporate [email protected]
            close
            open [email protected],5.11-0
            add depend type=incorporate fmri=osnet-incorporation
            add depend type=incorporate fmri=osnet-incorporation@1
            add depend type=incorporate [email protected]
            close
            open [email protected],5.11-0
            add depend type=incorporate fmri=osnet-incorporation
            add depend type=incorporate fmri=osnet-incorporation@1
            add depend type=incorporate [email protected]
            close
            open [email protected],5.11-0
            add depend type=incorporate [email protected]
            close
            open [email protected],5.11-0
            add depend type=incorporate [email protected]
            close
            open [email protected],5.11-0
            add depend type=incorporate [email protected]
            close
       """

        def test_15713570(self):
                """If an unincorporated package has its dependency obsoleted
                by a doubly nested incorporation with multiple levels of
                incorporation, there is no useful error message generated"""

                self.pkgsend_bulk(self.rurl, self.bug_15713570)
                self.image_create(self.rurl)

                self.pkg("install -v [email protected] oldcompiler")
                self.pkg("list")
                self.pkg("verify")
                self.pkg("update -v entire", exit=4)
                self.pkg("list")
                self.pkg("verify")
                self.pkg("update -v entire@2", exit=1)

                self.assert_("oldcompiler" in self.errout and
                    "oldperl" in self.errout,
                    "error message does not mention oldcompiler and oldperl packages")


class TestPkgInstallMultiObsolete(pkg5unittest.ManyDepotTestCase):
        """Tests involving obsolete packages and multiple publishers."""

        obs = """
            open stem@1
            add set name=pkg.obsolete value=true
            close
        """

        nonobs = """
            open stem@1
            close
        """

        persistent_setup = True

        def setUp(self):
                pkg5unittest.ManyDepotTestCase.setUp(self, ["test1", "test2"])
                self.rurl1 = self.dcs[1].get_repo_url()
                self.rurl2 = self.dcs[2].get_repo_url()

        def test_01(self):
                """If an obsolete package is found in a preferred publisher and
                a non-obsolete package of the same name is found in a
                non-preferred publisher, pick the preferred pub as usual """

                self.helper_01("install")
                self.helper_01("exact-install")

        def helper_01(self, install_cmd):
                self.pkgsend_bulk(self.rurl1, self.obs)
                self.pkgsend_bulk(self.rurl2, self.nonobs)

                self.image_create(self.rurl1, prefix="test1")
                self.pkg("set-publisher -O " + self.rurl2 + " test2")
                self.pkg("list -a")

                self.pkg("%s stem" % install_cmd, exit=4) # noting to do since it's obs
                # We should choose the obsolete package, which means nothing
                # gets installed.
                self.pkg("list", exit=1)

        def test_02(self):
                """Same as test_01, but now we have ambiguity in the package
                names.  While at first blush we might follow the same rule as in
                test_01 (choose the preferred publisher), in this case, we can't
                figure out which package from the preferred publisher we want,
                so the choice already isn't as straightforward, so we choose the
                non-obsolete package."""

                self.helper_02("install")
                self.helper_02("exact-install")

        def helper_02(self, install_cmd):
                lobs = """
                    open some/stem@1
                    add set name=pkg.obsolete value=true
                    close
                """

                self.pkgsend_bulk(self.rurl1, (self.obs, lobs))
                self.pkgsend_bulk(self.rurl2, (self.nonobs, lobs))

                self.image_create(self.rurl1, prefix="test1")
                self.pkg("set-publisher -O " + self.rurl2 + " test2")

                self.pkg("%s stem" % install_cmd, exit=1)


class TestPkgInstallMultiIncorp(pkg5unittest.ManyDepotTestCase):
        """Tests involving incorporations and multiple publishers."""

        incorporated_latest = """
            open [email protected]
            close
            open [email protected]
            close"""

        incorporated = """
            open [email protected]
            close
            open [email protected]
            close """

        incorporates = """
            open [email protected]
            add depend type=incorporate [email protected]
            close
            open [email protected]
            add depend type=incorporate [email protected]
            close"""

        persistent_setup = True

        def setUp(self):
                pkg5unittest.ManyDepotTestCase.setUp(self, ["test1", "test2"])
                self.rurl1 = self.dcs[1].get_repo_url()
                self.rurl2 = self.dcs[2].get_repo_url()

        def test_1_incorp_latest_older(self):
                """Ensure that if the newest release version of a package is
                available for an older branch that incorporate dependencies work
                as expected."""

                self.image_create(self.rurl1)
                self.pkgsend_bulk(self.rurl1, (self.incorporates,
                    self.incorporated, self.incorporated_latest))

                # First, install two incorporations that intersect such that
                # only the version before the latest branch can be installed.
                self.pkg("install userland-incorporation vim-incorporation")

                # Then, attempt to install vim; this should succeed even though
                # the newest version available is for an older branch.
                self.pkg("install [email protected]")

        def test_2_incorp_multi_pub(self):
                """Ensure that if another publisher offers newer packages that
                satisfy an incorporate dependency, but are rejected because of
                publisher selection, that the preferred publisher's package can
                still satisfy the incorporate."""

                self.image_create(self.rurl1)
                self.pkgsend_bulk(self.rurl1, (self.incorporates,
                    self.incorporated))
                self.pkgsend_bulk(self.rurl2, self.incorporated_latest)

                # First, install the incorporation.
                self.pkg("install userland-incorporation")

                # Next, add the second publisher.
                self.pkg("set-publisher -p %s" % self.rurl2)

                # Next, verify that first publisher's incorporated package can
                # be installed since it satisfies incorporate dependencies even
                # though second publisher's versions will be rejected.
                self.pkg("install //test1/vim")


class TestPkgInstallLicense(pkg5unittest.SingleDepotTestCase):
        """Tests involving one or more packages that require license acceptance
        or display."""

        persistent_depot = True

        # Tests in this suite use the read only data directory.
        need_ro_data = True

        baz10 = """
            open [email protected],5.11-0
            add license copyright.baz license=copyright.baz
            close """

        # First iteration has just a copyright.
        licensed10 = """
            open [email protected],5.11-0
            add depend type=require [email protected]
            add license copyright.licensed license=copyright.licensed
            close """

        # Second iteration has copyright that must-display and a new license
        # that doesn't require acceptance.
        licensed12 = """
            open [email protected],5.11-0
            add depend type=require [email protected]
            add file libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.so.1
            add license copyright.licensed license=copyright.licensed must-display=True
            add license license.licensed license=license.licensed
            close """

        # Third iteration now requires acceptance of license.
        licensed13 = """
            open [email protected],5.11-0
            add depend type=require [email protected]
            add file libc.so.1 mode=0555 owner=root group=bin path=/lib/libc.so.1
            add license copyright.licensed license=copyright.licensed must-display=True
            add license license.licensed license=license.licensed must-accept=True
            close """

        misc_files = ["copyright.baz", "copyright.licensed", "libc.so.1",
            "license.licensed", "license.licensed.addendum"]

        # Packages with copyright in non-ascii character
        nonascii10 = """
            open [email protected],5.11-0
            add license 88591enc.copyright license=copyright
            close """

        # Packages with copyright in non-ascii character
        utf8enc10 = """
            open [email protected],5.11-0
            add license utf8enc.copyright license=copyright
            close """

        # Packages with copyright in unsupported character set
        unsupported10 = """
            open [email protected],5.11-0
            add license unsupported.copyright license=copyright
            close """

        def setUp(self):
                pkg5unittest.SingleDepotTestCase.setUp(self, publisher="bobcat")
                self.make_misc_files(self.misc_files)

                # Use license with latin1 i.e 88591 encoding
                n_copyright = os.path.join(self.ro_data_root,
                    "88591enc.copyright")
                self.make_misc_files({"88591enc.copyright": n_copyright})

                # Use utf-8 encoding license
                utf_copyright = os.path.join(self.ro_data_root,
                    "utf8enc.copyright")
                self.make_misc_files({"utf8enc.copyright": utf_copyright})

                # Use unsupported license
                u_copyright = os.path.join(self.ro_data_root,
                    "unsupported.copyright")
                self.make_misc_files({"unsupported.copyright": u_copyright})

                self.plist = self.pkgsend_bulk(self.rurl, (self.licensed10,
                    self.licensed12, self.licensed13, self.baz10,
                    self.nonascii10, self.utf8enc10, self.unsupported10))


        def test_01_install_update(self):
                """Verifies that install and update handle license
                acceptance and display."""

                self.image_create(self.rurl, prefix="bobcat")

                # First, test the basic install case to see if a license that
                # does not require viewing or acceptance will be installed.
                self.pkg("install --parsable=0 [email protected]")
                self.assertEqualParsable(self.output,
                    add_packages=[self.plist[3], self.plist[0]], licenses=[
                        [self.plist[3], None,
                            [self.plist[3], "copyright.baz", "copyright.baz",
                            False, False]],
                        [self.plist[0], None,
                            [self.plist[0], "copyright.licensed",
                            "copyright.licensed", False, False]
                        ]])
                self.pkg("list")
                self.pkg("info [email protected] [email protected]")

                # Verify that --licenses include the license in output.
                self.pkg("install -n --licenses [email protected] | "
                    "grep 'license.licensed'")

                # Verify that licenses that require display are included in
                # -n output even if --licenses is not provided.
                self.pkg("install -n [email protected] | grep 'copyright.licensed'")

                # Next, check that an upgrade succeeds when a license requires
                # display and that the license will be displayed.
                self.pkg("install --parsable=0 [email protected]")
                self.assertEqualParsable(self.output,
                    change_packages=[[self.plist[0], self.plist[1]]], licenses=[
                        [self.plist[1],
                            None,
                            [self.plist[1], "license.licensed",
                            "license.licensed", False, False]],
                        [self.plist[1],
                            [self.plist[0], "copyright.licensed",
                            "copyright.licensed", False, False],
                            [self.plist[1], "copyright.licensed",
                            "copyright.licensed", False, True]]])

                # Next, check that an update fails if the user has not
                # specified --accept and a license requires acceptance.
                self.pkg("update -v", exit=6)
                # Check that asking for parsable output doesn't change this
                # requirement.
                self.pkg("update --parsable=0", exit=6)

                # Verify that licenses are not included in -n output if
                # --licenses is not provided.
                self.pkg("update -n | grep 'copyright.licensed", exit=1)

                # Verify that --licenses include the license in output.
                self.pkg("update -n --licenses | "
                    "grep 'license.licensed'")

                # Next, check that an update succeeds if the user has
                # specified --accept and a license requires acceptance.
                self.pkg("update --parsable=0 --accept")
                self.assertEqualParsable(self.output,
                    change_packages=[[self.plist[1], self.plist[2]]], licenses=[
                        [self.plist[2],
                            [self.plist[1], "license.licensed",
                            "license.licensed", False, False],
                            [self.plist[2], "license.licensed",
                            "license.licensed", True, False]]])
                self.pkg("info [email protected]")

        def test_02_bug_7127117(self):
                """Verifies that install with --parsable handles licenses
                with non-ascii & non UTF locale"""
                self.image_create(self.rurl, prefix="bobcat")

                self.pkg("install --parsable=0 [email protected]")
                self.pkg("install --parsable=0 [email protected]")
                self.pkg("install --parsable=0 [email protected]")


class TestActionErrors(pkg5unittest.SingleDepotTestCase):
        """This set of tests is intended to verify that the client will handle
        image state and action errors gracefully during install or uninstall
        operations.  Unlike the client API version of these tests, the CLI only
        needs to be tested for failure cases since it uses the client API."""

        # Teardown the test root every time.
        persistent_setup = False

        dir10 = """
            open [email protected],5.11-0
            add dir path=dir mode=755 owner=root group=bin
            close """

        dir11 = """
            open [email protected],5.11-0
            add dir path=dir mode=750 owner=root group=bin
            close """

        # Purposefully omits depend on [email protected].
        filesub10 = """
            open [email protected],5.11-0
            add file tmp/file path=dir/file mode=755 owner=root group=bin
            close """

        filesub11 = """
            open [email protected],5.11-0
            add file tmp/file path=dir/file mode=444 owner=root group=bin
            close """

        # Dependency providing file intentionally omitted.
        hardlink10 = """
            open [email protected],5.11-0
            add hardlink path=hardlink target=file
            close """

        # Empty packages suitable for corruption testing.
        foo10 = """
            open [email protected],5.11-0
            close """

        unsupp10 = """
            open [email protected]
            add depend type=require [email protected]
            close """

        misc_files = ["tmp/file"]

        def setUp(self):
                pkg5unittest.SingleDepotTestCase.setUp(self)
                self.make_misc_files(self.misc_files)

                plist = self.pkgsend_bulk(self.rurl, (self.dir10, self.dir11,
                    self.filesub10, self.filesub11, self.hardlink10))

                self.plist = {}
                for p in plist:
                        pfmri = fmri.PkgFmri(p, "5.11")
                        self.plist[pfmri.pkg_name] = pfmri

        @staticmethod
        def __write_empty_file(target, mode=644, owner="root", group="bin"):
                f = open(target, "wb")
                f.write("\n")
                f.close()
                os.chmod(target, mode)
                owner = portable.get_user_by_name(owner, "/", True)
                group = portable.get_group_by_name(group, "/", True)
                os.chown(target, owner, group)

        def test_00_directory(self):
                """Verify that directory install fails as expected when it has
                been replaced with a link prior to install."""

                self.image_create(self.rurl)

                # The dest_dir's installed path.
                dest_dir_name = "dir"
                dest_dir = os.path.join(self.get_img_path(), dest_dir_name)

                # Directory replaced with a link (fails for install).
                self.__write_empty_file(dest_dir + ".src")
                os.symlink(dest_dir + ".src", dest_dir)
                self.pkg("install %s" % dest_dir_name, exit=1)

        def test_01_file(self):
                """Verify that file install works as expected when its parent
                directory has been replaced with a link."""

                self.image_create(self.rurl)

                # File's parent directory replaced with a link.
                self.pkg("install dir")
                src = os.path.join(self.get_img_path(), "dir")
                os.mkdir(os.path.join(self.get_img_path(), "export"))
                new_src = os.path.join(os.path.dirname(src), "export", "dir")
                shutil.move(src, os.path.dirname(new_src))
                os.symlink(new_src, src)
                self.pkg("install [email protected]", exit=1)

        def test_02_hardlink(self):
                """Verify that hardlink install fails as expected when
                hardlink target is missing."""

                self.image_create(self.rurl)

                # Hard link target is missing (failure expected).
                self.pkg("install hardlink", exit=1)

        def __populate_repo(self, unsupp_content=None):
                # Publish a package and then add some unsupported action data
                # to the repository's copy of the manifest and catalog.
                sfmri = self.pkgsend_bulk(self.rurl, self.unsupp10)[0]

                if unsupp_content is None:
                        # No manipulation required.
                        return

                pfmri = fmri.PkgFmri(sfmri)
                repo = self.get_repo(self.dcs[1].get_repodir())
                mpath = repo.manifest(pfmri)
                with open(mpath, "ab+") as mfile:
                        mfile.write(unsupp_content + "\n")

                mcontent = None
                with open(mpath, "rb") as mfile:
                        mcontent = mfile.read()

                cat = repo.get_catalog("test")
                cat.log_updates = False

                # Update the catalog signature.
                entry = cat.get_entry(pfmri)
                entry["signature-sha-1"] = manifest.Manifest.hash_create(
                    mcontent)

                # Update the catalog actions.
                dpart = cat.get_part("catalog.dependency.C", must_exist=True)
                entry = dpart.get_entry(pfmri)
                entry["actions"].append(unsupp_content)

                # Write out the new catalog.
                cat.save()

        def test_03_unsupported(self):
                """Verify that packages with invalid or unsupported actions are
                handled gracefully.
                """

                # Base package needed for tests.
                self.pkgsend_bulk(self.rurl, self.foo10)

                # Verify that a package with unsupported content doesn't cause
                # a problem.
                newact = "depend type=new-type [email protected]"

                # Now create a new image and verify that pkg install will fail
                # for the unsupported package, but succeed for the supported
                # one.
                self.__populate_repo(newact)
                self.image_create(self.rurl)
                self.pkg("install [email protected]")
                self.pkg("install [email protected]", exit=1)
                self.pkg("uninstall foo")
                self.pkg("install [email protected] [email protected]", exit=1)

                # Verify that a package with invalid content behaves the same.
                newact = "depend notvalid"
                self.__populate_repo(newact)
                self.pkg("refresh --full")
                self.pkg("install [email protected]")
                self.pkg("install [email protected]", exit=1)
                self.pkg("uninstall foo")

                # Now verify that if a newer version of the unsupported package
                # is found that is supported, it can be installed.
                self.__populate_repo()
                self.pkg("refresh --full")
                self.pkg("install [email protected] [email protected]")
                self.pkg("uninstall foo unsupported")

        def test_04_loop(self):
                """Verify that if a directory or file is replaced with a link
                that targets itself (resulting in ELOOP) pkg fails gracefully.
                """

                # Create an image and install a package delivering a file.
                self.image_create(self.rurl)
                self.pkg("install [email protected] [email protected]")

                # Now replace the file with a link that points to itself.
                def create_link_loop(fpath):
                        if os.path.isfile(fpath):
                                portable.remove(fpath)
                        else:
                                shutil.rmtree(fpath)
                        cwd = os.getcwd()
                        os.chdir(os.path.dirname(fpath))
                        os.symlink(os.path.basename(fpath),
                            os.path.basename(fpath))
                        os.chdir(cwd)

                fpath = self.get_img_file_path("dir/file")
                create_link_loop(fpath)

                # Verify that pkg verify gracefully fails if traversing a
                # link targeting itself.
                self.pkg("verify", exit=1)

                # Verify that pkg succeeds if attempting to update a
                # package containing a file replaced with a link loop.
                self.pkg("update filesub")
                self.pkg("verify")

                # Now remove the package delivering the file and replace the
                # directory with a link loop.
                self.pkg("uninstall filesub")
                fpath = self.get_img_file_path("dir")
                create_link_loop(fpath)

                # Verify that pkg verify gracefully fails if traversing a
                # link targeting itself.
                self.pkg("verify", exit=1)

                # Verify that pkg gracefully fails if attempting to update
                # a package containing a directory replace with a link loop.
                self.pkg("update", exit=1)


class TestConflictingActions(_TestHelper, pkg5unittest.SingleDepotTestCase):
        """This set of tests verifies that packages which deliver conflicting
        actions into the same name in a namespace cannot be installed
        simultaneously."""

        pkg_boring10 = """
            open [email protected],5.11-0
            close """

        pkg_dupfiles = """
            open dupfiles@0,5.11-0
            add file tmp/file1 path=dir/pathname mode=0755 owner=root group=bin
            add file tmp/file2 path=dir/pathname mode=0755 owner=root group=bin
            close
        """

        pkg_dupfilesp1 = """
            open dupfilesp1@0,5.11-0
            add file tmp/file1 path=dir/pathname mode=0755 owner=root group=bin
            close
        """

        pkg_dupfilesp2 = """
            open dupfilesp2@0,5.11-0
            add file tmp/file2 path=dir/pathname mode=0755 owner=root group=bin
            close
        """

        pkg_dupfilesp2v2 = """
            open dupfilesp2@2,5.11-0
            close
        """

        pkg_dupfilesp3 = """
            open dupfilesp3@0,5.11-0
            add file tmp/file2 path=dir/pathname mode=0755 owner=root group=bin
            close
        """

        pkg_dupfilesp4 = """
            open dupfilesp4@0,5.11-0
            add file tmp/file3 path=dir/pathname mode=0755 owner=root group=bin
            close
        """

        pkg_dupotherfilesp1 = """
            open dupotherfilesp1@0,5.11-0
            add file tmp/file1 path=dir/namepath mode=0755 owner=root group=bin
            close
        """

        pkg_dupotherfilesp2 = """
            open dupotherfilesp2@0,5.11-0
            add file tmp/file2 path=dir/namepath mode=0755 owner=root group=bin
            close
        """

        pkg_dupotherfilesp2v2 = """
            open dupotherfilesp2@2,5.11-0
            close
        """

        pkg_dupotherfilesp3 = """
            open dupotherfilesp3@0,5.11-0
            add file tmp/file3 path=dir/namepath mode=0755 owner=root group=bin
            close
        """

        pkg_identicalfiles = """
            open identicalfiles@0,5.11-0
            add file tmp/file1 path=dir/pathname mode=0755 owner=root group=bin
            add file tmp/file1 path=dir/pathname mode=0755 owner=root group=bin
            close
        """

        pkg_overlaid = """
            open overlaid@0,5.11-0
            add dir path=etc mode=0755 owner=root group=root
            add file tmp/file1 path=etc/pam.conf mode=644 owner=root group=sys preserve=true overlay=allow
            close
            open overlaid@1,5.11-0
            add dir path=etc mode=0755 owner=root group=root
            add file tmp/file1 path=etc/pam.conf mode=644 owner=root group=sys preserve=renamenew overlay=allow
            close
            open overlaid@2,5.11-0
            add dir path=etc mode=0755 owner=root group=root
            add file tmp/file3 path=etc/pam.conf mode=644 owner=root group=sys preserve=renamenew overlay=allow
            close
            open overlaid@3,5.11-0
            add set name=pkg.renamed value=true
            add depend type=require fmri=overlaid-renamed@3
            add depend type=exclude fmri=overlaid-renamed@4
            close
            open overlaid-renamed@3,5.11-0
            add depend type=optional fmri=overlaid@3
            add dir path=etc mode=0755 owner=root group=root
            add file tmp/file3 original_name=overlaid:etc/pam.conf path=etc/pam.conf mode=644 owner=root group=sys preserve=renamenew overlay=allow
            close
            open [email protected],5.11-0
            add depend type=optional fmri=overlaid@3
            add dir path=etc mode=0755 owner=root group=root
            add dir path=etc/pam mode=0755 owner=root group=root
            add file tmp/file4 original_name=overlaid:etc/pam.conf path=etc/pam/pam.conf mode=644 owner=root group=sys preserve=renamenew
            close
            open [email protected],5.11-0
            add depend type=optional fmri=overlaid@3
            add dir path=etc mode=0755 owner=root group=root
            add dir path=etc/pam mode=0755 owner=root group=root
            add file tmp/file4 original_name=overlaid:etc/pam.conf path=etc/pam/pam.conf mode=644 owner=root group=sys preserve=renamenew overlay=allow
            close
        """

        # 'overlay' is ignored unless 'preserve' is also set.
        pkg_invalid_overlaid = """
            open invalid-overlaid@0,5.11-0
            add file tmp/file1 path=etc/pam.conf mode=644 owner=root group=sys overlay=allow
            close
        """

        pkg_overlayer = """
            open overlayer@0,5.11-0
            add file tmp/file2 path=etc/pam.conf mode=644 owner=root group=sys preserve=true overlay=true
            close
        """

        pkg_overlayer_move = """
            open overlayer-move@0,5.11-0
            add file tmp/file2 path=etc/pam.conf mode=644 owner=root group=sys preserve=true overlay=true
            close
            open overlayer-move@1,5.11-0
            add file tmp/file3 path=etc/pam/pam.conf mode=644 owner=root group=sys preserve=true overlay=true original_name=overlayer-move:etc/pam.conf
            close
        """

        pkg_overlayer_update = """
            open overlayer-update@0,5.11-0
            add file tmp/file1 path=etc/pam.conf mode=644 owner=root group=sys overlay=true
            close
            open overlayer-update@1,5.11-0
            add file tmp/file2 path=etc/pam.conf mode=644 owner=root group=sys preserve=true overlay=true
            close
            open overlayer-update@2,5.11-0
            add file tmp/file3 path=etc/pam.conf mode=644 owner=root group=sys preserve=renameold overlay=true
            close
            open overlayer-update@3,5.11-0
            add file tmp/file4 path=etc/pam.conf mode=644 owner=root group=sys preserve=renamenew overlay=true
            close
        """

        pkg_multi_overlayer = """
            open multi-overlayer@0,5.11-0
            add file tmp/file2 path=etc/pam.conf mode=644 owner=root group=sys preserve=true overlay=true
            close
        """

        # overlaying file is treated as conflicting file if its mode, owner, and
        # group attributes don't match the action being overlaid
        pkg_mismatch_overlayer = """
            open mismatch-overlayer@0,5.11-0
            add file tmp/file2 path=etc/pam.conf mode=640 owner=root group=bin preserve=true overlay=true
            close
        """

        # overlaying file is treated as conflicting file if it doesn't set overlay=true
        # even if file being overlaid allows overlay.
        pkg_invalid_overlayer = """
            open invalid-overlayer@0,5.11-0
            add file tmp/file2 path=etc/pam.conf mode=644 owner=root group=sys preserve=true
            close
        """

        pkg_unpreserved_overlayer = """
            open unpreserved-overlayer@0,5.11-0
            add file tmp/unpreserved path=etc/pam.conf mode=644 owner=root group=sys overlay=true
            close
        """

        pkgremote_pkg1 = """
            open pkg1@0,5.11-0
            add file tmp/file1 path=remote mode=644 owner=root group=sys
            close
        """

        pkgremote_pkg2 = """
            open pkg2@0,5.11-0
            add file tmp/file2 path=remote mode=644 owner=root group=sys
            close
        """

        pkg_dupfilesv1 = """
            open dupfilesv1@0,5.11-0
            add set name=variant.arch value=sparc value=i386
            add dir path=dir/pathname mode=0755 owner=root group=bin variant.arch=i386
            close
        """

        pkg_dupfilesv2 = """
            open dupfilesv2@0,5.11-0
            add dir path=dir/pathname mode=0755 owner=root group=bin
            close
        """

        pkg_dupfilesv3 = """
            open dupfilesv3@0,5.11-0
            add set name=variant.arch value=sparc value=i386
            add dir path=dir/pathname mode=0777 owner=root group=bin variant.arch=sparc
            close
        """

        pkg_dupfilesv4 = """
            open dupfilesv4@0,5.11-0
            add set name=variant.arch value=sparc value=i386
            add file tmp/file1 path=dir/pathname mode=0777 owner=root group=bin variant.arch=sparc
            add file tmp/file2 path=dir/pathname mode=0777 owner=root group=bin variant.arch=sparc
            add file tmp/file3 path=dir/pathname mode=0777 owner=root group=bin variant.arch=i386
            close
        """

        pkg_dupfilesv5 = """
            open dupfilesv5@0,5.11-0
            add set name=variant.opensolaris.zone value=global value=nonglobal
            add file tmp/file1 path=dir/pathname mode=0777 owner=root group=bin variant.opensolaris.zone=nonglobal
            close
        """

        pkg_dupfilesv6 = """
            open dupfilesv6@0,5.11-0
            add file tmp/file2 path=dir/pathname mode=0755 owner=root group=bin
            close
        """

        pkg_dupfilesf1 = """
            open dupfilesf1@0,5.11-0
            add dir path=dir/pathname mode=0755 owner=root group=bin facet.devel=true
            close
        """

        pkg_dupfilesf2 = """
            open dupfilesf2@0,5.11-0
            add dir path=dir/pathname mode=0755 owner=root group=bin facet.devel=false
            close
        """

        pkg_dupfilesf3 = """
            open dupfilesf3@0,5.11-0
            add dir path=dir/pathname mode=0755 owner=root group=bin
            close
        """

        pkg_dupfilesf4 = """
            open dupfilesf4@0,5.11-0
            add file tmp/file1 path=dir/pumpkin mode=0755 owner=root group=bin
            add file tmp/file2 path=dir/pumpkin mode=0755 owner=root group=bin facet.devel=true
            close
        """

        pkg_duppathfilelink = """
            open duppath-filelink@0,5.11-0
            add file tmp/file1 path=dir/pathname mode=0755 owner=root group=bin
            add link path=dir/pathname target=dir/other
            close
        """

        pkg_duplink = """
            open duplink@0,5.11-0
            add link path=dir/pathname target=dir/other
            close
        """

        pkg_dupmultitypes1 = """
            open dupmultitypes@1,5.11-0
            add link path=multitypepath target=dir/other
            add file tmp/file1 path=multitypepath mode=0644 owner=root group=bin
            add dir path=multitypepath mode=0755 owner=root group=bin
            close
        """

        pkg_dupmultitypes2 = """
            open dupmultitypes@2,5.11-0
            add dir path=multitypepath mode=0755 owner=root group=bin
            close
        """

        pkg_dupmultitypes3_0 = """
            open dupmultitypes3@0,5.11-0
            add link path=multitypepath target=blah
            add link path=multitypepath target=blah
            close
        """

        pkg_dupmultitypes3_1 = """
            open dupmultitypes3@1,5.11-0
            add dir path=multitypepath mode=0755 owner=root group=bin
            add dir path=multitypepath mode=0755 owner=root group=bin
            close
        """

        pkg_duppathnonidenticallinks = """
            open duppath-nonidenticallinks@0,5.11-0
            add link path=dir/pathname target=dir/something
            add link path=dir/pathname target=dir/other
            close
        """

        pkg_duppathnonidenticallinksp1 = """
            open duppath-nonidenticallinksp1@0,5.11-0
            add link path=dir/pathname target=dir/something
            close
        """

        pkg_duppathnonidenticallinksp2 = """
            open duppath-nonidenticallinksp2@0,5.11-0
            add link path=dir/pathname target=dir/other
            close
        """

        pkg_duppathnonidenticallinksp2v1 = """
            open duppath-nonidenticallinksp2@1,5.11-0
            close
        """

        pkg_duppathnonidenticaldirs = """
            open duppath-nonidenticaldirs@0,5.11-0
            add dir path=dir/pathname owner=root group=root mode=0755
            add dir path=dir/pathname owner=root group=bin mode=0711
            close
        """

        pkg_duppathalmostidenticaldirs = """
            open duppath-almostidenticaldirs@0,5.11-0
            add dir path=dir/pathname owner=root group=root mode=0755
            add dir path=dir/pathname owner=root group=root mode=755
            close
        """

        pkg_implicitdirs = """
            open implicitdirs@0,5.11-0
            add file tmp/file1 path=usr/bin/something mode=0755 owner=root group=bin
            add file tmp/file2 path=usr/bin mode=0755 owner=root group=bin
            close
        """

        pkg_implicitdirs2 = """
            open implicitdirs2@0,5.11-0
            add file tmp/file1 path=usr/bin/something mode=0755 owner=root group=bin
            add dir path=usr/bin mode=0700 owner=root group=bin
            close
        """

        pkg_implicitdirs3 = """
            open implicitdirs3@0,5.11-0
            add file tmp/file1 path=usr/bin/other mode=0755 owner=root group=bin
            close
        """

        pkg_implicitdirs4 = """
            open implicitdirs4@0,5.11-0
            add file tmp/file1 path=usr/bin/something mode=0755 owner=root group=bin
        """

        pkg_implicitdirs5 = """
            open implicitdirs5@0,5.11-0
            add dir path=usr/bin mode=0755 owner=root group=sys
        """

        pkg_implicitdirs6 = """
            open implicitdirs6@0,5.11-0
            add dir path=usr/bin mode=0755 owner=root group=bin
        """

        pkg_implicitdirs7 = """
            open implicitdirs7@0,5.11-0
            add file tmp/file1 path=usr/bin mode=0755 owner=root group=bin
        """

        pkg_dupdir = """
            open dupdir@0,5.11-0
            add dir path=dir/pathname owner=root group=bin mode=0755
            close
        """

        pkg_dupdirv1 = """
            open dupdir@1,5.11-0
            close
        """

        pkg_dupdirnowhere = """
            open dupdirnowhere@0,5.11-0
            add dir path=dir/pathname owner=root group=bin mode=0755
            close
        """

        pkg_dupdirp1 = """
            open dupdirp1@1,5.11-0
            add dir path=dir owner=root group=bin mode=0755
            close
        """

        pkg_dupdirp2 = """
            open dupdirp2@1,5.11-0
            add dir path=dir owner=root group=sys mode=0755
            close
        """

        pkg_dupdirp2_2 = """
            open dupdirp2@2,5.11-0
            add dir path=dir owner=root group=bin mode=0755
            close
        """

        pkg_dupdirp3 = """
            open dupdirp3@1,5.11-0
            add dir path=dir owner=root group=bin mode=0750
            close
        """

        pkg_dupdirp4 = """
            open dupdirp4@1,5.11-0
            add dir path=dir owner=root group=sys mode=0750
            close
        """

        pkg_dupdirp5 = """
            open dupdirp5@1,5.11-0
            add dir path=dir owner=root group=other mode=0755
            close
        """

        pkg_dupdirp6 = """
            open dupdirp6@1,5.11-0
            add dir path=dir owner=root group=other mode=0755
            close
        """

        pkg_dupdirp7 = """
            open dupdirp7@1,5.11-0
            add file tmp/file1 path=dir/file owner=root group=other mode=0755
            close
        """

        pkg_dupdirp8 = """
            open dupdirp8@1,5.11-0
            add dir path=dir owner=root group=bin mode=0755
            close
        """

        pkg_dupdirp8_2 = """
            open dupdirp8@2,5.11-0
            add dir path=dir owner=root group=sys mode=0755
            close
        """

        pkg_dupdirp9 = """
            open dupdirp9@1,5.11-0
            add dir path=var owner=root group=other mode=0755
            add dir path=usr owner=root group=other mode=0755
            close
        """

        pkg_dupdirp10 = """
            open dupdirp10@1,5.11-0
            add dir path=var owner=root group=bin mode=0755
            add dir path=usr owner=root group=bin mode=0755
            close
        """

        pkg_dupdirp11 = """
            open dupdirp11@1,5.11-0
            add dir path=usr/bin owner=root group=bin mode=0755
            add dir path=var/zap owner=root group=bin mode=0755
            close
        """

        pkg_dupdirp12 = """
            open dupdirp12@1,5.11-0
            add dir path=usr/bin owner=root group=bin mode=0755
            add legacy pkg=dupdirp9
            close
        """

        pkg_userdb = """
            open userdb@0,5.11-0
            add file tmp/passwd mode=0644 owner=root group=bin path=etc/passwd preserve=true
            add file tmp/group mode=0644 owner=root group=bin path=etc/group preserve=true
            add file tmp/shadow mode=0600 owner=root group=bin path=etc/shadow preserve=true
            add file tmp/ftpusers mode=0644 owner=root group=bin path=etc/ftpd/ftpusers preserve=true
            close
        """

        userdb_files = {
            "tmp/passwd": """\
root:x:0:0::/root:/usr/bin/bash
daemon:x:1:1::/:
bin:x:2:2::/usr/bin:
sys:x:3:3::/:
adm:x:4:4:Admin:/var/adm:
""",
            "tmp/group": """\
root::0:
other::1:root
bin::2:root,daemon
sys::3:root,bin,adm
adm::4:root,daemon
""",
            "tmp/shadow": """\
root:9EIfTNBp9elws:13817::::::
daemon:NP:6445::::::
bin:NP:6445::::::
sys:NP:6445::::::
adm:NP:6445::::::
""",
            "tmp/ftpusers": """\
root
bin
sys
adm
"""
        }

        pkg_dupuser = """
            open dupuser@0,5.11-0
            add user username=kermit group=adm gcos-field="kermit 1"
            add user username=kermit group=adm gcos-field="kermit 2"
            close
        """

        pkg_dupuserp1 = """
            open dupuserp1@0,5.11-0
            add user username=kermit group=adm gcos-field="kermit 1"
            close
        """

        pkg_dupuserp2 = """
            open dupuserp2@0,5.11-0
            add user username=kermit group=adm gcos-field="kermit 2"
            close
        """

        pkg_dupuserp2v2 = """
            open dupuserp2@1,5.11-0
            close
        """

        pkg_dupuserp3 = """
            open dupuserp3@0,5.11-0
            add user username=kermit group=adm gcos-field="kermit 2"
            close
        """

        pkg_dupuserp4 = """
            open dupuserp4@0,5.11-0
            add user username=kermit group=adm gcos-field="kermit 4"
            close
        """

        pkg_otheruser = """
            open otheruser@0,5.11-0
            add user username=fozzie group=adm home-dir=/export/home/fozzie
            close
        """

        pkg_othergroup = """
            open othergroup@0,5.11-0
            add group groupname=fozzie gid=87
            add group groupname=fozzie gid=88
            add group groupname=fozzie gid=89
            close
        """

        pkg_othergroup1 = """
            open othergroup@1,5.11-0
            add group groupname=fozzie gid=87
            close
        """

        pkg_driverdb = """
            open driverdb@0,5.11-0
            add file tmp/devlink.tab path=etc/devlink.tab mode=0644 owner=root group=bin
            add file tmp/driver_aliases path=etc/driver_aliases mode=0644 owner=root group=bin
            add file tmp/driver_classes path=etc/driver_classes mode=0644 owner=root group=bin
            add file tmp/minor_perm path=etc/minor_perm mode=0644 owner=root group=bin
            add file tmp/name_to_major path=etc/name_to_major mode=0644 owner=root group=bin
            add file tmp/device_policy path=etc/security/device_policy mode=0644 owner=root group=bin
            add file tmp/extra_privs path=etc/security/extra_privs mode=0644 owner=root group=bin
            close
        """

        driverdb_files = {
            "tmp/devlink.tab": "",
            "tmp/driver_aliases": "",
            "tmp/driver_classes": "",
            "tmp/minor_perm": "",
            "tmp/name_to_major": "",
            "tmp/device_policy": "",
            "tmp/extra_privs": ""
        }

        pkg_dupdrv = """
            open dupdriver@0,5.11-0
            add driver name=asy perms="* 0666 root sys" perms="*,cu 0600 uucp uucp"
            add driver name=asy perms="* 0666 root sys" alias=pci11c1,480
            close
        """

        pkg_dupdepend1 = """
            open dupdepend1@0,5.11-0
            add depend type=require fmri=dupfilesp1
            add depend type=require fmri=dupfilesp1
            close
        """

        pkg_dupdepend2 = """
            open dupdepend2@0,5.11-0
            add depend type=require fmri=dupfilesp1
            add depend type=incorporate fmri=dupfilesp1
            close
        """

        pkg_dupdepend3 = """
            open dupdepend3@0,5.11-0
            add depend type=require fmri=dupfilesp1@0-0
            add depend type=require fmri=dupfilesp1@0-0
            close
        """

        pkg_dupdepend4 = """
            open dupdepend4@0,5.11-0
            add depend type=require fmri=dupfilesp1@0-0
            add depend type=incorporate fmri=dupfilesp1@0-0
            close
        """

        pkg_tripledupfilea = """
            open tripledupfilea@0,5.11-0
            add set name=variant.foo value=one
            add file tmp/file1 path=file owner=root group=other mode=0755
            close
        """

        pkg_tripledupfileb = """
            open tripledupfileb@0,5.11-0
            add set name=variant.foo value=one value=two
            add file tmp/file2 path=file owner=root group=other mode=0755
            close
        """

        pkg_tripledupfilec = """
            open tripledupfilec@0,5.11-0
            add set name=variant.foo value=one value=two
            add file tmp/file3 path=file owner=root group=other mode=0755
            close
        """

        pkg_variantedtypes = """
            open vpath@0,5.11-0
            add set name=variant.foo value=one value=two
            add dir group=bin mode=0755 owner=root path=boot/platform \
                variant.foo=two
            add link path=boot/platform target=../platform variant.foo=one

            close
        """

        misc_files = ["tmp/file1", "tmp/file2", "tmp/file3", "tmp/file4",
            "tmp/unpreserved"]

        # Keep the depots around for the duration of the entire class
        persistent_setup = True

        def setUp(self):
                pkg5unittest.SingleDepotTestCase.setUp(self)
                self.make_misc_files(self.misc_files)
                self.make_misc_files(self.userdb_files)
                self.make_misc_files(self.driverdb_files)

                pkgs = []
                for objname in dir(self.__class__):
                        obj = getattr(self, objname)
                        if objname.startswith("pkg_") and type(obj) == str:
                                pkgs.append(obj)

                for i in xrange(20):
                        s = """
                                open massivedupdir%d@0,5.11-0
                                add dir path=usr owner=root group=%%s mode=%%s zig=%%s
                                close
                        """ % i

                        if i == 14:
                                s = s % ("root", "0750", "zag")
                        elif i in (1, 9):
                                s = s % ("sys", "0750", "zag")
                        elif i in (3, 8, 12, 17):
                                s = s % ("root", "0755", "zag")
                        else:
                                s = s % ("sys", "0755", "zig")

                        pkgs.append(s)

                self.plist = self.pkgsend_bulk(self.rurl, pkgs)

        def test_multiple_files_install(self):
                """Test the behavior of pkg(1) when multiple file actions
                deliver to the same pathname."""

                self.image_create(self.rurl)

                # Duplicate files in the same package
                self.pkg("install dupfiles", exit=1)

                # Duplicate files in different packages, but in the same
                # transaction
                self.pkg("install dupfilesp1 dupfilesp2@0", exit=1)

                # Duplicate files in different packages, in different
                # transactions
                self.pkg("install dupfilesp1")
                self.pkg("install dupfilesp2@0", exit=1)

                # Test that being in a duplicate file situation doesn't break
                # you completely and allows you to add and remove other packages
                self.pkg("-D broken-conflicting-action-handling=1 install dupfilesp2@0")
                self.pkg("install implicitdirs2")
                self.pkg("uninstall implicitdirs2")

                # If the packages involved get upgraded but leave the actions
                # themselves alone, we should be okay.
                self.pkg("install dupfilesp2 dupfilesp3")
                self.pkg("verify", exit=1)

                # Test that removing one of two offending actions reverts the
                # system to a clean state.
                self.pkg("uninstall dupfilesp3")
                self.pkg("verify")

                # You should be able to upgrade to a fixed set of packages in
                # order to move past the problem, too.
                self.pkg("uninstall dupfilesp2")
                self.pkg("-D broken-conflicting-action-handling=1 install dupfilesp2@0")
                self.pkg("update")
                self.pkg("verify")

                # If we upgrade to a version of a conflicting package that no
                # longer has the conflict, but at the same time introduce a new
                # file action at the path with different contents, we should
                # fail.
                self.pkg("uninstall dupfilesp2")
                self.pkg("-D broken-conflicting-action-handling=1 install dupfilesp2@0")
                self.pkg("install dupfilesp2 dupfilesp4", exit=1)

                # Removing one of more than two offending actions can't do much
                # of anything, but should leave the system alone.
                self.pkg("uninstall '*'")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupfilesp1 dupfilesp2@0 dupfilesp3")
                # Verify should report a duplicate action error on dupfilesp1,
                # dupfilesp2 and dupfilesp3 and shouldn't report it was failing
                # due to hashes being wrong.
                self.pkg("verify", exit=1)
                out1, err1 = self.output, self.errout
                for i, l in enumerate(self.plist):
                    if l.startswith("pkg://test/dupfilesp1"):
                        index = i
                expected = "\n  {0}\n  {1}\n  {2}".format(
                    self.plist[index], self.plist[index + 1], self.plist[index + 3])
                self.assert_(expected in err1, err1)
                self.assert_("Hash" not in out1)
                self.pkg("uninstall dupfilesp3")
                # Removing dupfilesp3, verify should still report a duplicate
                # action error on dupfilesp1 and dupfilesp2.
                self.pkg("verify", exit=1)
                out2, err2 = self.output, self.errout
                expected = "\n  {0}\n  {1}".format(
                    self.plist[index], self.plist[index + 1])
                self.assert_(expected in err2)
                self.assert_("Hash" not in out2)

                # Removing all but one of the offending actions should get us
                # back to sanity.
                self.pkg("uninstall '*'")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupfilesp1 dupfilesp2@0 dupfilesp3")
                self.pkg("uninstall dupfilesp3 dupfilesp2")
                self.pkg("verify")

                # Make sure we handle cleaning up multiple files properly.
                self.pkg("uninstall '*'")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupfilesp1 dupfilesp2@0 dupotherfilesp1 dupotherfilesp2")
                self.pkg("uninstall dupfilesp2 dupotherfilesp2")
                self.pkg("verify")

                # Re-use the overlay packages for some preserve testing.
                self.pkg("install overlaid@0")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "invalid-overlayer")
                # We may have been able to lay down the package, but because the
                # file is marked preserve=true, we didn't actually overwrite
                self.file_contains("etc/pam.conf", "file2")
                self.pkg("uninstall invalid-overlayer")
                self.file_contains("etc/pam.conf", "file2")

                # Make sure we get rid of all implicit directories.
                self.pkg("uninstall '*'")
                self.pkg("install implicitdirs3 implicitdirs4")
                self.pkg("uninstall implicitdirs3 implicitdirs4")

                if os.path.isdir(os.path.join(self.get_img_path(), "usr/bin")):
                        self.assert_(False, "Directory 'usr/bin' should not exist")

                if os.path.isdir(os.path.join(self.get_img_path(), "usr")):
                        self.assert_(False, "Directory 'usr' should not exist")

                # Make sure identical actions don't cause problems
                self.pkg("install -nv identicalfiles", exit=1)

                # Trigger a bug similar to 17943 via duplicate files.
                self.pkg("publisher")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupfilesp1@0 dupfilesp2@0 dupfilesp3@0 dupotherfilesp1@0 "
                    "dupotherfilesp2@0 dupotherfilesp3@0")
                self.pkg("update")

                # If an uninstall causes a fixup to happen and we can't because
                # we lost the cached files and the repo is down, make sure we
                # fail before actually uninstalling anything.
                self.dc.start()
                self.pkgsend_bulk(self.durl, (self.pkgremote_pkg1,
                    self.pkgremote_pkg2))
                self.image_create(self.durl)
                self.pkg("install pkg1")
                self.pkg("-D broken-conflicting-action-handling=1 install pkg2")
                self.pkg("verify pkg2")
                self.dc.stop()
                self.pkg("uninstall pkg2", exit=1)
                self.pkg("verify pkg2")

        def test_overlay_files_install(self):
                """Test the behaviour of pkg(1) when actions for editable files
                overlay other actions."""

                # Ensure that overlay is allowed for file actions when one
                # action has specified preserve attribute and overlay=allow,
                # and *one* (only) other action has specified overlay=true
                # (preserve does not have to be set).
                self.image_create(self.rurl)

                # Ensure boring package is installed as conflict checking is
                # bypassed (and thus, overlay semantics) if all packages are
                # removed from an image.
                self.pkg("install boring")

                # Should fail because one action specified overlay=allow,
                # but not preserve (it isn't editable).
                self.pkg("install invalid-overlaid")
                self.pkg("install overlayer", exit=1)
                self.pkg("uninstall invalid-overlaid")

                # Should fail because one action is overlayable but overlaying
                # action doesn't declare its intent to overlay.
                self.pkg("install overlaid@0")
                self.file_contains("etc/pam.conf", "file1")
                self.pkg("install invalid-overlayer", exit=1)

                # Should fail because one action is overlayable but overlaying
                # action mode, owner, and group attributes don't match.
                self.pkg("install mismatch-overlayer", exit=1)

                # Should succeed because one action is overlayable and
                # overlaying action declares its intent to overlay.
                self.pkg("contents -m overlaid")
                self.pkg("contents -mr overlayer")
                self.pkg("install --parsable=0 overlayer")
                self._assertEditables(
                    installed=["etc/pam.conf"],
                )
                self.file_contains("etc/pam.conf", "file2")

                # Should fail because multiple actions are not allowed to
                # overlay a single action.
                self.pkg("install multi-overlayer", exit=1)

                # Should succeed even though file is different than originally
                # delivered since original package permits file modification.
                self.pkg("verify overlaid overlayer")

                # Should succeed because package delivering overlayable file
                # permits modification and because package delivering overlay
                # file permits modification.
                self.file_append("etc/pam.conf", "zigit")
                self.pkg("verify overlaid overlayer")

                # Verify that the file isn't touched on uninstall of the
                # overlaying package if package being overlaid is still
                # installed.
                self.pkg("uninstall --parsable=0 overlayer")
                self._assertEditables()
                self.file_contains("etc/pam.conf", "zigit")
                self.file_contains("etc/pam.conf", "file2")

                # Verify that removing the last package delivering an overlaid
                # file removes the file.
                self.pkg("uninstall --parsable=0 overlaid")
                self._assertEditables(
                    removed=["etc/pam.conf"],
                )
                self.file_doesnt_exist("etc/pam.conf")

                # Verify that installing both packages at the same time results
                # in only the overlaying file being delivered.
                self.pkg("install --parsable=0 overlaid@0 overlayer")
                self._assertEditables(
                    installed=["etc/pam.conf"],
                )
                self.file_contains("etc/pam.conf", "file2")

                # Verify that the file isn't touched on uninstall of the
                # overlaid package if overlaying package is still installed.
                self.file_append("etc/pam.conf", "zigit")
                self.pkg("uninstall --parsable=0 overlaid")
                self._assertEditables()
                self.file_contains("etc/pam.conf", "file2")
                self.file_contains("etc/pam.conf", "zigit")

                # Re-install overlaid package and verify that file content
                # does not change.
                self.pkg("install --parsable=0 overlaid@0")
                self._assertEditables()
                self.file_contains("etc/pam.conf", "file2")
                self.file_contains("etc/pam.conf", "zigit")
                self.pkg("uninstall --parsable=0 overlaid overlayer")
                self._assertEditables(
                    removed=["etc/pam.conf"],
                )

                # Should succeed because one action is overlayable and
                # overlaying action declares its intent to overlay even
                # though the overlaying action isn't marked with preserve.
                self.pkg("install -nvv overlaid@0 unpreserved-overlayer")
                self.pkg("install --parsable=0 overlaid@0 unpreserved-overlayer")
                self._assertEditables(
                    installed=["etc/pam.conf"],
                )
                self.file_contains("etc/pam.conf", "unpreserved")

                # Should succeed because overlaid action permits modification
                # and contents matches overlaying action.
                self.pkg("verify overlaid unpreserved-overlayer")

                # Should succeed even though file has been modified since
                # overlaid action permits modification.
                self.file_append("etc/pam.conf", "zigit")
                self.pkg("verify overlaid")

                # Should fail because overlaying action does not permit
                # modification.
                self.pkg("verify unpreserved-overlayer", exit=1)

                # Should revert to content delivered by overlaying action.
                self.pkg("fix unpreserved-overlayer")
                self.file_contains("etc/pam.conf", "unpreserved")
                self.file_doesnt_contain("etc/pam.conf", "zigit")

                # Should revert to content delivered by overlaying action.
                self.file_append("etc/pam.conf", "zigit")
                self.pkg("revert /etc/pam.conf")
                self.file_contains("etc/pam.conf", "unpreserved")
                self.file_doesnt_contain("etc/pam.conf", "zigit")
                self.pkg("uninstall --parsable=0 unpreserved-overlayer")
                self._assertEditables()

                # Should revert to content delivered by overlaid action.
                self.file_contains("etc/pam.conf", "unpreserved")
                self.pkg("revert /etc/pam.conf")
                self.file_contains("etc/pam.conf", "file1")

                # Install overlaying package, then update overlaid package and
                # verify that file content does not change if only preserve
                # attribute changes.
                self.pkg("install --parsable=0 unpreserved-overlayer")
                self._assertEditables(
                    installed=["etc/pam.conf"],
                )
                self.file_contains("etc/pam.conf", "unpreserved")
                self.pkg("install --parsable=0 overlaid@1")
                self._assertEditables()
                self.file_contains("etc/pam.conf", "unpreserved")
                self.pkg("uninstall --parsable=0 overlaid")
                self._assertEditables()

                # Now update overlaid package again, and verify that file
                # content does not change even though overlaid content has.
                self.pkg("install --parsable=0 overlaid@2")
                self._assertEditables()
                self.file_contains("etc/pam.conf", "unpreserved")

                # Now update overlaid package again this time as part of a
                # rename, and verify that file content does not change even
                # though file has moved between packages.
                self.pkg("install --parsable=0 overlaid@3")
                self._assertEditables()
                self.file_contains("etc/pam.conf", "unpreserved")

                # Verify that unpreserved overlay is not salvaged when both
                # overlaid and overlaying package are removed at the same time.
                # (Preserved files are salvaged if they have been modified on
                # uninstall.)

                # Ensure directory is empty before testing.
                api_inst = self.get_img_api_obj()
                img_inst = api_inst.img
                sroot = os.path.join(img_inst.imgdir, "lost+found")
                shutil.rmtree(sroot)

                # Verify etc directory not found after uninstall.
                self.pkg("uninstall --parsable=0 overlaid-renamed "
                    "unpreserved-overlayer")
                self._assertEditables(
                    removed=['etc/pam.conf'],
                )
                salvaged = [
                    n for n in os.listdir(sroot)
                    if n.startswith("etc")
                ]
                self.assertEqualDiff(salvaged, [])

                # Next, update overlaid package again this time as part of a
                # file move.  Verify that the configuration file exists at both
                # the new location and the old location, that the content has
                # not changed in either, and that the new configuration exists
                # as expected as ".new".
                self.pkg("install --parsable=0 overlaid-renamed@3 "
                    "unpreserved-overlayer")
                self._assertEditables(
                    installed=['etc/pam.conf'],
                )
                self.pkg("install -nvv [email protected]")
                self.pkg("install --parsable=0 [email protected]")
                self._assertEditables(
                    moved=[['etc/pam.conf', 'etc/pam/pam.conf']],
                    installed=['etc/pam/pam.conf.new'],
                )
                self.file_contains("etc/pam.conf", "unpreserved")
                self.file_contains("etc/pam/pam.conf", "unpreserved")
                self.file_contains("etc/pam/pam.conf.new", "file4")

                # Verify etc/pam.conf not salvaged after uninstall as overlay
                # file has not been changed.
                self.pkg("uninstall --parsable=0 overlaid-renamed "
                    "unpreserved-overlayer")
                self._assertEditables(
                    removed=['etc/pam.conf', 'etc/pam/pam.conf'],
                )
                salvaged = [
                    n for n in os.listdir(os.path.join(sroot, "etc"))
                    if n.startswith("pam.conf")
                ]
                self.assertEqualDiff(salvaged, [])

                # Next, repeat the same set of tests performed above for renames
                # and moves with an overlaying, preserved file.
                #
                # Install overlaying package, then update overlaid package and
                # verify that file content does not change if only preserve
                # attribute changes.
                self.pkg("install --parsable=0 overlayer")
                self._assertEditables(
                    installed=['etc/pam.conf'],
                )
                self.file_contains("etc/pam.conf", "file2")
                self.file_append("etc/pam.conf", "zigit")
                self.pkg("install --parsable=0 overlaid@1")
                self._assertEditables()
                self.file_contains("etc/pam.conf", "zigit")
                self.pkg("uninstall --parsable=0 overlaid")
                self._assertEditables()

                # Now update overlaid package again, and verify that file
                # content does not change even though overlaid content has.
                self.pkg("install --parsable=0 overlaid@2")
                self._assertEditables()
                self.file_contains("etc/pam.conf", "zigit")

                # Now update overlaid package again this time as part of a
                # rename, and verify that file content does not change even
                # though file has moved between packages.
                self.pkg("install --parsable=0 overlaid@3")
                self._assertEditables()
                self.file_contains("etc/pam.conf", "zigit")

                # Verify that preserved overlay is salvaged when both overlaid
                # and overlaying package are removed at the same time.
                # (Preserved files are salvaged if they have been modified on
                # uninstall.)

                # Ensure directory is empty before testing.
                api_inst = self.get_img_api_obj()
                img_inst = api_inst.img
                sroot = os.path.join(img_inst.imgdir, "lost+found")
                shutil.rmtree(sroot)

                # Verify etc directory found after uninstall.
                self.pkg("uninstall --parsable=0 overlaid-renamed overlayer")
                self._assertEditables(
                    removed=['etc/pam.conf'],
                )
                salvaged = [
                    n for n in os.listdir(sroot)
                    if n.startswith("etc")
                ]
                self.assertEqualDiff(salvaged, ["etc"])

                # Next, update overlaid package again, this time as part of a
                # file move where the overlay attribute was dropped.  Verify
                # that the configuration file exists at both the new location
                # and the old location, that the content has not changed in
                # either, and that the new configuration exists as expected as
                # ".new".
                self.pkg("install --parsable=0 overlaid-renamed@3 overlayer")
                self._assertEditables(
                    installed=['etc/pam.conf'],
                )
                self.file_append("etc/pam.conf", "zigit")
                self.pkg("install --parsable=0 [email protected]")
                self._assertEditables(
                    moved=[['etc/pam.conf', 'etc/pam/pam.conf']],
                    installed=['etc/pam/pam.conf.new'],
                )
                self.file_contains("etc/pam.conf", "zigit")
                self.file_contains("etc/pam/pam.conf", "zigit")
                self.file_contains("etc/pam/pam.conf.new", "file4")
                self.pkg("uninstall --parsable=0 overlaid-renamed overlayer")
                self._assertEditables(
                    removed=['etc/pam.conf', 'etc/pam/pam.conf'],
                )

                # Next, update overlaid package again, this time as part of a
                # file move.  Verify that the configuration file exists at both
                # the new location and the old location, that the content has
                # not changed in either, and that the new configuration exists
                # as expected as ".new".
                self.pkg("install --parsable=0 overlaid-renamed@3 overlayer")
                self._assertEditables(
                    installed=['etc/pam.conf'],
                )
                self.file_append("etc/pam.conf", "zigit")
                self.pkg("install --parsable=0 [email protected]")
                self._assertEditables(
                    moved=[['etc/pam.conf', 'etc/pam/pam.conf']],
                    installed=['etc/pam/pam.conf.new'],
                )
                self.file_contains("etc/pam.conf", "zigit")
                self.file_contains("etc/pam/pam.conf", "zigit")
                self.file_contains("etc/pam/pam.conf.new", "file4")

                # Next, downgrade the package and verify that if an overlaid
                # file moves back to its original location, the content of the
                # overlay file will not change.
                self.pkg("update --parsable=0 overlaid-renamed@3")
                self._assertEditables(
                    removed=['etc/pam/pam.conf'],
                )
                self.file_contains("etc/pam.conf", "zigit")

                # Now upgrade again for remaining tests.
                self.pkg("install --parsable=0 [email protected]")
                self._assertEditables(
                    moved=[['etc/pam.conf', 'etc/pam/pam.conf']],
                    installed=['etc/pam/pam.conf.new'],
                )

                # Verify etc/pam.conf and etc/pam/pam.conf salvaged after
                # uninstall as overlay file and overlaid file is different from
                # packaged.
                shutil.rmtree(sroot)
                self.pkg("uninstall --parsable=0 overlaid-renamed overlayer")
                self._assertEditables(
                    removed=['etc/pam.conf', 'etc/pam/pam.conf'],
                )
                salvaged = sorted(
                    n for n in os.listdir(os.path.join(sroot, "etc"))
                    if n.startswith("pam")
                )
                # Should have three entries; one should be 'pam' directory
                # (presumably containing pam.conf-X...), another a file starting
                # with 'pam.conf', and finally a 'pam-XXX' directory containing
                # the 'pam.conf.new-XXX'.
                self.assertEqualDiff(salvaged[0], "pam")
                self.assert_(salvaged[1].startswith("pam-"),
                    msg=str(salvaged))
                self.assert_(salvaged[2].startswith("pam.conf"),
                    msg=str(salvaged))

                # Next, install overlaid package and overlaying package, then
                # upgrade each to a version where the file has changed
                # locations and verify that the content remains intact.
                self.pkg("install --parsable=0 overlaid@0 overlayer-move@0")
                self._assertEditables(
                    installed=['etc/pam.conf'],
                )
                self.file_append("etc/pam.conf", "zigit")
                self.pkg("install --parsable=0 overlaid@3")
                self._assertEditables()
                self.file_contains("etc/pam.conf", "zigit")
                self.pkg("install --parsable=0 [email protected] "
                    "overlayer-move@1")
                self._assertEditables(
                    moved=[['etc/pam.conf', 'etc/pam/pam.conf']],
                )
                self.file_contains("etc/pam/pam.conf", "zigit")

                # Next, downgrade overlaid-renamed and overlaying package to
                # versions where the file is restored to its original location
                # and verify that the content is reverted to the original
                # overlay version since this is a downgrade.
                self.pkg("update --parsable=0 overlaid-renamed@3 "
                    "overlayer-move@0")
                self._assertEditables(
                    removed=['etc/pam/pam.conf'],
                    installed=['etc/pam.conf'],
                )
                self.file_contains("etc/pam.conf", "file2")
                self.pkg("uninstall --parsable=0 overlaid-renamed overlayer-move")
                self._assertEditables(
                    removed=['etc/pam.conf'],
                )

                # Next, install overlaid package and overlaying package and
                # verify preserve acts as expected for overlay package as it is
                # updated.
                self.pkg("install --parsable=0 overlaid@2 overlayer-update@0")
                self._assertEditables(
                    installed=['etc/pam.conf'],
                )
                self.file_contains("etc/pam.conf", "file1")
                # unpreserved -> preserved
                self.pkg("install --parsable=0 overlayer-update@1")
                self._assertEditables(
                    updated=['etc/pam.conf'],
                )
                self.file_contains("etc/pam.conf", "file2")
                self.file_append("etc/pam.conf", "zigit")
                # preserved -> renameold
                self.pkg("install --parsable=0 overlayer-update@2")
                self._assertEditables(
                    moved=[['etc/pam.conf', 'etc/pam.conf.old']],
                    installed=['etc/pam.conf'],
                )
                self.file_doesnt_contain("etc/pam.conf", "zigit")
                self.file_contains("etc/pam.conf.old", "zigit")
                self.file_append("etc/pam.conf", "zagat")
                # renameold -> renamenew
                self.pkg("install --parsable=0 overlayer-update@3")
                self._assertEditables(
                    installed=['etc/pam.conf.new'],
                )
                self.file_contains("etc/pam.conf", "zagat")
                self.file_contains("etc/pam.conf.new", "file4")

        def test_different_types_install(self):
                """Test the behavior of pkg(1) when multiple actions of
                different types deliver to the same pathname."""

                self.image_create(self.rurl)

                # In the same package
                self.pkg("install duppath-filelink", exit=1)

                # In different packages, in the same transaction
                self.pkg("install dupfilesp1 duplink", exit=1)

                # In different packages, in different transactions
                self.pkg("install dupfilesp1")
                self.pkg("install duplink", exit=1)

                # Does removal of one of the busted packages get us out of the
                # situation?
                self.pkg("uninstall '*'")
                self.pkg("-D broken-conflicting-action-handling=1 install dupfilesp1 duplink")
                self.pkg("verify", exit=1)
                self.pkg("uninstall dupfilesp1")
                self.pkg("verify")
                self.pkg("-D broken-conflicting-action-handling=1 install dupfilesp1")
                self.pkg("uninstall duplink")
                self.pkg("verify")

                # Implicit directory conflicts with a file
                self.pkg("uninstall '*'")
                self.pkg("install implicitdirs", exit=1)

                # Implicit directory coincides with a delivered directory
                self.pkg("install implicitdirs2")

                # Make sure that we don't die trying to fixup a directory using
                # an implicit directory action.
                self.pkg("uninstall '*'")
                self.pkg("install implicitdirs4")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "implicitdirs7")
                self.pkg("uninstall implicitdirs7")
                # XXX We don't currently fix up anything beneath a directory
                # that was restored, so we have to do it by hand.
                os.mkdir("%s/usr/bin" % self.img_path())
                shutil.copy("%s/tmp/file1" % self.test_root,
                    "%s/usr/bin/something" % self.img_path())
                owner = portable.get_user_by_name("root", self.img_path(), True)
                group = portable.get_group_by_name("bin", self.img_path(), True)
                os.chown("%s/usr/bin/something" % self.img_path(), owner, group)
                os.chmod("%s/usr/bin/something" % self.img_path(), 0755)
                self.pkg("verify")

                # Removing one of more than two offending actions can't do much
                # of anything, but should leave the system alone.
                self.pkg("uninstall '*'")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupfilesp1 duplink dupdir@0")
                tmap = {
                    stat.S_IFIFO: "fifo",
                    stat.S_IFCHR: "character device",
                    stat.S_IFDIR: "directory",
                    stat.S_IFBLK: "block device",
                    stat.S_IFREG: "regular file",
                    stat.S_IFLNK: "symbolic link",
                    stat.S_IFSOCK: "socket",
                }
                thepath = "%s/dir/pathname" % self.img_path()
                fmt = stat.S_IFMT(os.lstat(thepath).st_mode)
                # XXX The checks here rely on verify failing due to action types
                # not matching what's on the system; they should probably report
                # duplicate actions instead.  Checking the output text is a bit
                # ugly, too, but we do need to make sure that the two problems
                # become one.
                self.pkg("verify", exit=1)
                verify_type_re = "File Type: '(.*?)' should be '(.*?)'"
                matches = re.findall(verify_type_re, self.output)
                # We make sure that what got reported is correct -- two actions
                # of different types in conflict with whatever actually got laid
                # down.
                self.assert_(len(matches) == 2)
                whatis = matches[0][0]
                self.assert_(matches[1][0] == whatis)
                self.assert_(whatis == tmap[fmt])
                shouldbe = set(["symbolic link", "regular file", "directory"]) - \
                    set([whatis])
                self.assert_(set([matches[0][1], matches[1][1]]) == shouldbe)
                # Now we uninstall one of the packages delivering a type which
                # isn't what's on the filesystem.  The filesystem should remain
                # unchanged, but one of the errors should go away.
                if whatis == "directory":
                        self.pkg("uninstall duplink")
                else:
                        self.pkg("uninstall dupdir")
                self.pkg("verify", exit=1)
                matches = re.findall(verify_type_re, self.output)
                self.assert_(len(matches) == 1)
                nfmt = stat.S_IFMT(os.lstat(thepath).st_mode)
                self.assert_(nfmt == fmt)

                # Now we do the same thing, but we uninstall the package
                # delivering the type which *is* what's on the filesystem.  This
                # should also leave the filesystem alone, even though what's
                # there will match *neither* of the remaining installed
                # packages.
                self.pkg("uninstall '*'")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupfilesp1 duplink dupdir@0")
                fmt = stat.S_IFMT(os.lstat(thepath).st_mode)
                self.pkg("verify", exit=1)
                matches = re.findall(verify_type_re, self.output)
                self.assert_(len(matches) == 2)
                whatis = matches[0][0]
                self.assert_(matches[1][0] == whatis)
                self.assert_(whatis == tmap[fmt])
                shouldbe = set(["symbolic link", "regular file", "directory"]) - \
                    set([whatis])
                self.assert_(set([matches[0][1], matches[1][1]]) == shouldbe)
                if whatis == "directory":
                        self.pkg("uninstall dupdir")
                elif whatis == "symbolic link":
                        self.pkg("uninstall duplink")
                elif whatis == "regular file":
                        self.pkg("uninstall dupfilesp1")
                self.pkg("verify", exit=1)
                matches = re.findall(verify_type_re, self.output)
                self.assert_(len(matches) == 2)
                nfmt = stat.S_IFMT(os.lstat(thepath).st_mode)
                self.assert_(nfmt == fmt)

                # Go from multiple conflicting types down to just one type.
                # This also tests the case where a package version being newly
                # installed gets fixed at the same time.
                self.pkg("uninstall '*'")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupmultitypes@1")
                self.pkg("install dupmultitypes")
                self.pkg("verify")

                # Upgrading from multiple instances of one refcounted type to
                # multiple instances of another (here, link to directory) should
                # succeed.
                self.pkg("uninstall '*'")
                self.pkg("install dupmultitypes3@0")
                self.pkg("update")

        def test_conflicting_attrs_fs_install(self):
                """Test the behavior of pkg(1) when multiple non-file actions of
                the same type deliver to the same pathname, but whose other
                attributes differ."""

                self.image_create(self.rurl)

                # One package, two links with different targets
                self.pkg("install duppath-nonidenticallinks", exit=1)

                # One package, two directories with different perms
                self.pkg("install duppath-nonidenticaldirs", exit=1)

                # One package, two dirs with same modes expressed two ways
                self.pkg("install duppath-almostidenticaldirs")

                # One package delivers a directory explicitly, another
                # implicitly.
                self.pkg("install implicitdirs2 implicitdirs3")
                self.pkg("verify")

                self.pkg("uninstall '*'")

                # Make sure that we don't die trying to fixup a directory using
                # an implicit directory action.
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "implicitdirs4 implicitdirs5 implicitdirs6")
                self.pkg("uninstall implicitdirs5")
                self.pkg("verify")

                self.pkg("uninstall '*'")

                # Make sure that we don't die trying to fixup a directory using
                # an implicit directory action when that's all that's left.
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "implicitdirs4 implicitdirs5 implicitdirs6")
                self.pkg("uninstall implicitdirs5 implicitdirs6")
                self.pkg("verify")

                self.pkg("uninstall '*'")

                # If two packages deliver conflicting directories and another
                # package delivers that directory implicitly, make sure the
                # third package isn't blamed.
                self.pkg("install implicitdirs4 implicitdirs5 implicitdirs6",
                    exit=1)
                self.assert_("implicitdirs4" not in self.errout)

                # Two packages, two links with different targets, installed at
                # once
                self.pkg("install duppath-nonidenticallinksp1 "
                    "duppath-nonidenticallinksp2@0", exit=1)

                # Two packages, two links with different targets, installed
                # separately
                self.pkg("install duppath-nonidenticallinksp1")
                self.pkg("install duppath-nonidenticallinksp2@0", exit=1)

                self.pkg("uninstall '*'")

                # If we get into a broken state, can we get out of it?
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "duppath-nonidenticallinksp1 duppath-nonidenticallinksp2@0")
                self.pkg("verify", exit=1)
                self.pkg("install duppath-nonidenticallinksp2")
                self.pkg("verify")

                # If we get into a broken state, can we make it a little bit
                # better by uninstalling one of the packages?  Removing dupdir5
                # here won't reduce the number of different groups under which
                # dir is delivered, but does reduce the number of actions
                # delivering it.
                self.pkg("uninstall '*'")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupdirp1 dupdirp2@1 dupdirp5 dupdirp6")
                self.pkg("uninstall dupdirp5")
                self.pkg("verify", exit=1)

                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupdirp5")
                # Make sure we can install a package delivering an implicit
                # directory that's currently in conflict.
                self.pkg("install dupdirp7")
                # And make sure we can uninstall it again.
                self.pkg("uninstall dupdirp7")

                # Removing the remaining conflicts in a couple of steps should
                # result in a verifiable system.
                self.pkg("uninstall dupdirp2")
                self.pkg("uninstall dupdirp5 dupdirp6")
                self.pkg("verify")

                # Add everything back in, remove everything but one variant of
                # the directory and an implicit directory, and verify.
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupdirp2@1 dupdirp5 dupdirp6 dupdirp7")
                self.pkg("uninstall dupdirp2 dupdirp5 dupdirp6")
                self.pkg("verify")

                # Get us into a saner state by upgrading.
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupdirp2@1 dupdirp5 dupdirp6")
                self.pkg("update dupdirp2@2")

                # Get us into a sane state by upgrading.
                self.pkg("uninstall dupdirp2 dupdirp5 dupdirp6")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupdirp2@1")
                self.pkg("update dupdirp2@2")
                self.pkg("verify")

                # We start in a sane state, but the update would result in
                # conflict, though no more actions deliver the path in
                # question.
                self.pkg("uninstall '*'")
                self.pkg("install dupdirp1 dupdirp8@1")
                self.pkg("update", exit=1)

                # How about removing one of the conflicting packages?  We'll
                # remove the package which doesn't match the state on disk.
                self.pkg("uninstall '*'")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "duppath-nonidenticallinksp1 duppath-nonidenticallinksp2@0")
                link = os.readlink("%s/dir/pathname" % self.img_path())
                if link == "dir/something":
                        self.pkg("uninstall duppath-nonidenticallinksp2")
                else:
                        self.pkg("uninstall duppath-nonidenticallinksp1")
                self.pkg("verify")

                # Now we'll try removing the package which *does* match the
                # state on disk.  The code should clean up after us.
                self.pkg("uninstall '*'")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "duppath-nonidenticallinksp1 duppath-nonidenticallinksp2@0")
                link = os.readlink("%s/dir/pathname" % self.img_path())
                if link == "dir/something":
                        self.pkg("uninstall duppath-nonidenticallinksp1")
                else:
                        self.pkg("uninstall duppath-nonidenticallinksp2")
                self.pkg("verify")

                # Let's try a duplicate directory delivered with all sorts of
                # crazy conflicts!
                self.pkg("uninstall '*'")
                self.pkg("install dupdirp1 dupdirp2@1 dupdirp3 dupdirp4", exit=1)

                pkgs = " ".join("massivedupdir%d" % x for x in xrange(20))
                self.pkg("install %s" % pkgs, exit=1)

                # Trigger bug 17943: we install packages with conflicts in two
                # directories (p9, p10).  We also install a package (p11) which
                # delivers those directories implicitly.  Then remove the last,
                # triggering the stack trace associated with the bug.
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupdirp9 dupdirp10 dupdirp11")
                self.pkg("uninstall dupdirp11")

                # Do the same, but with a package that delivers var implicitly
                # via a legacy action.
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupdirp12")
                self.pkg("uninstall dupdirp12")

        def test_conflicting_attrs_fs_varcets(self):
                """Test the behavior of pkg(1) when multiple non-file actions of
                the same type deliver to the same pathname, but differ in their
                variants or facets."""

                install_cmd = "install"
                self.image_create(self.rurl)

                # Two packages delivering the same directory, one under the
                # current architecture, the other not tagged with an arch
                # variant.
                self.pkg("%s dupfilesv1 dupfilesv2" % install_cmd)
                self.dir_exists("dir/pathname")

                # Two packages delivering the same directory with different
                # attributes -- one under the current architecture, the other
                # tagged with another arch variant.
                self.pkg("uninstall '*'")
                self.pkg("%s dupfilesv1 dupfilesv3" % install_cmd)
                if platform.processor() == "sparc":
                        self.dir_exists("dir/pathname", mode=0777)
                else:
                        self.dir_exists("dir/pathname", mode=0755)

                # Two packages delivering a file at the same path where one is
                # tagged only for non-global zones should install successfully
                # together in a global zone.
                self.pkg("uninstall '*'")
                self.pkg("%s dupfilesv5 dupfilesv6" % install_cmd)
                path = os.path.join(self.get_img_path(), "dir/pathname")
                try:
                        f = open(path)
                except OSError, e:
                        if e.errno == errno.ENOENT:
                                self.assert_(False, "File dir/pathname does not exist")
                        else:
                                raise
                self.assertEqual(f.read().rstrip(), "tmp/file2")
                f.close()

                # Two packages delivering the same directory, one with the
                # devel facet false, the other true.
                self.pkg("uninstall '*'")
                self.pkg("%s dupfilesf1 dupfilesf2" % install_cmd)
                self.dir_exists("dir/pathname")

                # Two packages delivering the same directory, one with the
                # devel facet true, the other without.
                self.pkg("uninstall '*'")
                self.pkg("%s dupfilesf1 dupfilesf3" % install_cmd)
                self.dir_exists("dir/pathname")

                # Two packages delivering the same directory, one with the
                # devel facet false, the other without.
                self.pkg("uninstall '*'")
                self.pkg("%s dupfilesf2 dupfilesf3" % install_cmd)
                self.dir_exists("dir/pathname")

        def test_conflicting_uninstall_publisher(self):
                """Test the behaviour of pkg(1) when attempting to remove
                conflicting packages from a publisher which has also been
                removed."""

                self.conflicting_uninstall_publisher_helper("install")
                self.conflicting_uninstall_publisher_helper("exact-install")

        def conflicting_uninstall_publisher_helper(self, install_cmd):
                self.image_create(self.rurl)
                # Dummy publisher so test publisher can be removed.
                self.pkg("set-publisher -P ignored")

                # If packages with conflicting actions are found during
                # uninstall, and the publisher of the package has been
                # removed, uninstall should still succeed.
                self.pkg("-D broken-conflicting-action-handling=1 %s "
                    "dupdirp1 dupdirp2@1" % install_cmd)
                self.pkg("unset-publisher test")
                self.pkg("uninstall dupdirp2")
                self.pkg("verify")

        def test_change_varcet(self):
                """Test the behavior of pkg(1) when changing a variant or a
                facet would cause the new image to contain conflicting
                actions."""

                # Create the image as an x86 image, as the first test only works
                # changing variant from x86 to sparc.
                self.image_create(self.rurl, variants={"variant.arch": "i386"})

                # The x86 variant is safe, but the sparc variant has two files
                # with the same pathname.
                self.pkg("install dupfilesv4")
                self.pkg("change-variant arch=sparc", exit=1)

                # With the devel facet turned off, the package is safe, but
                # turning it on would cause a duplicate file to be added.
                self.pkg("change-facet devel=false")
                self.pkg("install dupfilesf4")
                self.pkg("change-facet devel=true", exit=1)

        def test_change_variant_removes_package(self):
                """Test that a change-variant that removes a package and
                improves but doesn't fix a conflicting action situation is
                allowed."""

                self.image_create(self.rurl, variants={"variant.foo": "one"})
                self.pkg("install tripledupfileb")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "tripledupfilec")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "tripledupfilea")
                self.pkg("change-variant -vvv variant.foo=two")
                self.pkg("change-variant -vvv variant.foo=one", exit=1)

        def dir_exists(self, path, mode=None, owner=None, group=None):
                dir_path = os.path.join(self.get_img_path(), path)
                try:
                        st = os.stat(dir_path)
                except OSError, e:
                        if e.errno == errno.ENOENT:
                                self.assert_(False, "Directory %s does not exist" % path)
                        else:
                                raise
                if mode is not None:
                        self.assert_(stat.S_IMODE(st.st_mode) == mode)
                if owner is not None:
                        self.assert_(st.st_uid == owner)
                if group is not None:
                        self.assert_(st.st_gid == group)

        def test_multiple_users_install(self):
                """Test the behavior of pkg(1) when multiple user actions
                deliver the same user."""

                # This is largely identical to test_multiple_files; we may want
                # to commonize in the future.

                self.image_create(self.rurl)

                self.pkg("install userdb")

                # Duplicate users in the same package
                self.pkg("install dupuser", exit=1)

                # Duplicate users in different packages, but in the same
                # transaction
                self.pkg("install dupuserp1 dupuserp2@0", exit=1)

                # Duplicate users in different packages, in different
                # transactions
                self.pkg("install dupuserp1")
                self.pkg("install dupuserp2@0", exit=1)

                # Test that being in a duplicate user situation doesn't break
                # you completely and allows you to add and remove other packages
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupuserp2@0")
                self.pkg("verify", exit=1)
                self.pkg("install otheruser")
                self.pkg("uninstall otheruser")
                self.pkg("verify", exit=1)

                # If the packages involved get upgraded but leave the actions
                # themselves alone, we should be okay.
                self.pkg("install dupuserp2 dupuserp3")
                self.pkg("verify", exit=1)

                # Test that removing one of two offending actions reverts the
                # system to a clean state.
                self.pkg("uninstall dupuserp3")
                self.pkg("verify")

                # You should be able to upgrade to a fixed set of packages in
                # order to move past the problem, too.
                self.pkg("uninstall dupuserp2")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupuserp2@0")
                self.pkg("update")
                self.pkg("verify")

                # If we upgrade to a version of a conflicting package that no
                # longer has the conflict, but at the same time introduce a new
                # conflicting user action, we should fail.
                self.pkg("uninstall dupuserp2")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupuserp2@0")
                self.pkg("install dupuserp2 dupuserp4", exit=1)

                # Removing one of more than two offending actions can't do much
                # of anything, but should leave the system alone.
                self.image_destroy()
                self.image_create(self.rurl)
                self.pkg("install userdb")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupuserp1 dupuserp2@0 dupuserp3")
                self.pkg("verify", exit=1)
                out1 = self.output
                self.pkg("uninstall dupuserp3")
                self.pkg("verify", exit=1)
                out2 = self.output
                out2 = out2[out2.index("STATUS\n") + 7:]
                self.assert_(out2 in out1)

                # Removing all but one of the offending actions should get us
                # back to sanity.
                self.image_destroy()
                self.image_create(self.rurl)
                self.pkg("install userdb")
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "dupuserp1 dupuserp2@0 dupuserp3")
                self.pkg("uninstall dupuserp3 dupuserp2")
                self.pkg("verify")

                # Make sure we don't get confused when two actions in different
                # namespace groups but with the same key attribute value are
                # adjacent in the action cache.
                self.pkg("-D broken-conflicting-action-handling=1 install "
                    "otheruser othergroup@0")
                self.pkg("update othergroup")

        def test_multiple_drivers(self):
                """Test the behavior of pkg(1) when multiple driver actions
                deliver the same driver."""

                self.multiple_drivers_helper("install")
                self.multiple_drivers_helper("exact-install")

        def multiple_drivers_helper(self, install_cmd):
                self.image_create(self.rurl)

                self.pkg("%s driverdb" % install_cmd)

                self.pkg("%s dupdriver" % install_cmd, exit=1)

        def test_multiple_depend(self):
                """Test to make sure we can have multiple depend actions on
                (more or less) the same fmri"""

                self.multiple_depend_helper("install")
                self.multiple_depend_helper("exact-install")

        def multiple_depend_helper(self, install_cmd):
                self.image_create(self.rurl)

                # Two identical unversioned require dependencies
                self.pkg("%s dupdepend1" % install_cmd)

                # Two dependencies of different types on an identical
                # unversioned fmri
                self.pkg("%s dupdepend2" % install_cmd)

                # Two identical versioned require dependencies
                self.pkg("%s dupdepend3" % install_cmd)

                # Two dependencies of different types on an identical versioned
                # fmri
                self.pkg("%s dupdepend4" % install_cmd)

        def test_varianted_types(self):
                """Test that actions which would otherwise conflict but are
                delivered under different variants don't conflict."""

                self.varianted_types_helper("install")
                self.varianted_types_helper("exact-install")

        def varianted_types_helper(self, install_cmd):
                self.pkg_image_create(repourl=self.rurl,
                    additional_args="--variant foo=one")
                self.pkg("%s vpath" % install_cmd)


class TestPkgInstallExplicitInstall(pkg5unittest.SingleDepotTestCase):
        """Test pkg.depend.explicit-install action behaviors."""
        persistent_setup = True

        pkgs = (
                """
                    open [email protected],5.11-0
                    add depend type=group fmri=pkg:/A
                    close """,
                """
                    open [email protected],5.11-0
                    add depend type=incorporate fmri=pkg:/[email protected],5.11-0.1
                    close """,
                """
                    open [email protected],5.11-0.1
                    close """,
                """
                    open [email protected],5.11-0.1.1.0
                    add depend type=require fmri=pkg:/[email protected],5.11-0.1.1.0
                    close """,
                """
                    open [email protected],5.11-0.1.1.0
                    add set name=pkg.depend.explicit-install value=true
                    add depend type=incorporate fmri=pkg:/[email protected],5.11-0.1.1.0
                    close """,
        )

        pkgs2 = (
                 """
                    open [email protected],5.11-0.1.1.1
                    add depend type=require fmri=pkg:/[email protected],5.11-0.1.1.1
                    close """,
                """
                    open [email protected],5.11-0.1.1.1
                    add set name=pkg.depend.explicit-install value=false
                    add depend type=incorporate fmri=pkg:/[email protected],5.11-0.1.1.1
                    close """,
        )

        pkgs3 = (
                """
                    open [email protected],5.11-0.1.1.2
                    add depend type=require fmri=pkg:/[email protected],5.11-0.1.1.2
                    close """,
                """
                    open [email protected],5.11-0.1.1.2
                    add depend type=incorporate fmri=pkg:/[email protected],5.11-0.1.1.2
                    close """,
        )

        pkgs4 = (
                """
                    open [email protected]
                    add depend type=require-any fmri=pkg:/[email protected] fmri=pkg:/[email protected]
                    close """,
                """
                    open [email protected]
                    add depend type=require fmri=pkg:/[email protected],5.11-0.1
                    close """,
                """
                    open [email protected]
                    add set name=pkg.depend.explicit-install value=true
                    add depend type=require fmri=pkg:/[email protected],5.11-0.1
                    close """,
                """
                    open [email protected],5.11-0.1
                    close """,
        )

        pkgs5 = (
                """
                    open [email protected]
                    add depend type=require fmri=pkg:/[email protected]
                    close """,
                """
                    open [email protected]
                    add depend type=require fmri=pkg:/[email protected]
                    close """,
                """
                    open [email protected]
                    add set name=pkg.depend.explicit-install value=true
                    close """,
        )

        def setUp(self):
                pkg5unittest.SingleDepotTestCase.setUp(self)
                self.pkgsend_bulk(self.rurl, self.pkgs)

        def test_01_install(self):
                self.image_create(self.rurl, prefix="")
                # Test install works as expected.
                # This will fail because [email protected] has
                # pkg.depend.explicit-install set to true.
                self.pkg("install -v group incorp [email protected]", exit=1)
                self.pkg("install -v group incorp")
                self.pkg("verify")
                self.pkg("list -H")
                expected = \
                    "A    1.0-0.1    i--\n" \
                    "group    1.0-0    i--\n" \
                    "incorp    1.0-0    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)
                self.pkg("uninstall -v group incorp A")
                self.pkg("list -H", exit=1)

                # Test exact-install works as expected.
                # This will fail because [email protected] has
                # pkg.depend.explicit-install set to true.
                self.pkg("exact-install -v group incorp [email protected]", exit=1)
                self.pkg("exact-install -v group incorp")
                self.pkg("verify")
                self.pkg("list -H")
                output = self.reduceSpaces(self.output)
                self.assertEqualDiff(expected, output)

                # Test exact-install idr.
                self.pkg("exact-install -v idr")
                self.pkg("list -H")
                expected = \
                    "idr    1.0-0.1.1.0    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)

                self.pkg("install -v group")
                self.pkg("verify")
                self.pkg("list -H")
                expected = \
                    "A    1.0-0.1.1.0    i--\n" \
                    "group    1.0-0    i--\n" \
                    "idr    1.0-0.1.1.0    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)

                self.pkg("uninstall -v group idr A")
                self.pkg("list -H", exit=1)

                self.pkg("install -v idr")
                self.pkg("list -H")
                expected = \
                    "idr    1.0-0.1.1.0    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)

                self.pkg("install -v group")
                self.pkg("verify")
                self.pkg("list -H")
                expected = \
                    "A    1.0-0.1.1.0    i--\n" \
                    "group    1.0-0    i--\n" \
                    "idr    1.0-0.1.1.0    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)
                self.pkg("uninstall '*'")
                self.pkg("list -H", exit=1)

                self.pkgsend_bulk(self.rurl, self.pkgs2)
                self.pkg("install -v group incorp")
                self.pkg("verify")
                self.pkg("list -H")
                expected = \
                    "A    1.0-0.1.1.1    i--\n" \
                    "group    1.0-0    i--\n" \
                    "idr    1.0-0.1.1.1    i--\n" \
                    "incorp    1.0-0    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)

                self.pkgsend_bulk(self.rurl, self.pkgs3)
                # test updating all packages.
                self.pkg("update")
                self.pkg("verify")
                self.pkg("list -H")
                expected = \
                    "A    1.0-0.1.1.2    i--\n" \
                    "group    1.0-0    i--\n" \
                    "idr    1.0-0.1.1.2    i--\n" \
                    "incorp    1.0-0    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)
                self.pkg("uninstall '*'")

                self.pkgsend_bulk(self.rurl, self.pkgs4)
                # test require-any with pkg.depend.explicit-install tag.
                self.pkg("install C1")
                self.pkg("verify")
                self.pkg("list -H")
                expected = \
                    "C1    1.0    i--\n" \
                    "C2    1.0    i--\n" \
                    "C3    1.0-0.1    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)
                self.pkg("uninstall '*'")

                self.pkg("install C1 C2")
                self.pkg("verify")
                self.pkg("list -H")
                expected = \
                    "C1    1.0    i--\n" \
                    "C2    2.0    i--\n" \
                    "C3    1.0-0.1    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)

                # Test hierarchic dependencies.
                self.pkgsend_bulk(self.rurl, self.pkgs5)
                self.pkg("install -v [email protected]", exit=1)

        def test_02_updateReject(self):
                self.image_create(self.rurl, prefix="")
                self.pkgsend_bulk(self.rurl, self.pkgs2)
                self.pkgsend_bulk(self.rurl, self.pkgs3)
                self.pkg("install -v --reject idr group incorp")
                self.pkg("verify")
                self.pkg("list -H")
                expected = \
                    "A    1.0-0.1    i--\n" \
                    "group    1.0-0    i--\n" \
                    "incorp    1.0-0    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)

                self.pkg("exact-install -v --reject idr group")
                self.pkg("verify")
                self.pkg("list -H")
                expected = \
                    "A    1.0-0.1    i--\n" \
                    "group    1.0-0    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)

                # This will fail, because [email protected] is filtered.
                self.pkg("update -v --reject group [email protected]", exit=1)
                # Explicitly install [email protected].
                self.pkg("install [email protected]")
                # Update again.
                self.pkg("update -v --reject group [email protected]")
                self.pkg("verify")
                self.pkg("list -H")
                expected = \
                    "A    1.0-0.1.1.0    i--\n" \
                    "idr    1.0-0.1.1.0    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)

                # This will fail.
                self.pkg("update -v --reject idr", exit=1)
                self.pkg("update")
                self.pkg("verify")
                self.pkg("list -H")
                expected = \
                    "A    1.0-0.1.1.2    i--\n" \
                    "idr    1.0-0.1.1.2    i--\n"
                output = self.reduceSpaces(self.output)
                expected = self.reduceSpaces(expected)
                self.assertEqualDiff(expected, output)


class TestPkgUpdateDowngradeIncorp(pkg5unittest.ManyDepotTestCase):
        persistent_setup = True

        pkgs = (
                """
                    open [email protected],5.11-0
                    close """,
                """
                    open [email protected],5.11-0
                    close """,
                """
                    open [email protected],5.11-0
                    add set pkg.depend.install-hold=B
                    close """,
                """
                    open [email protected],5.11-0
                    add set pkg.depend.install-hold=B
                    close """,
                """
                    open [email protected],5.11-0
                    add set pkg.depend.install-hold=parent.C
                    close """,
                """
                    open [email protected],5.11-0
                    add set pkg.depend.install-hold=parent.C
                    close """,
                """
                    open [email protected],5.11-0
                    add depend type=incorporate [email protected]
                    close """,
                """
                    open [email protected],5.11-0
                    add depend type=incorporate [email protected]
                    close """,
                """
                    open [email protected],5.11-0
                    add depend type=incorporate [email protected]
                    close """,
                """
                    open [email protected],5.11-0
                    add depend type=incorporate [email protected]
                    close """,
                """
                    open [email protected],5.11-0
                    add depend type=incorporate [email protected]
                    close """,
                """
                    open [email protected],5.11-0
                    add depend type=incorporate [email protected]
                    close """,
                """
                    open [email protected],5.11-0
                    add set pkg.depend.install-hold=parent
                    add depend type=incorporate [email protected]
                    add depend type=incorporate [email protected]
                    close """,
                """
                    open [email protected],5.11-0
                    add set pkg.depend.install-hold=parent
                    add depend type=incorporate [email protected]
                    add depend type=incorporate [email protected]
                    close """,

                """
                    open [email protected],5.11-0
                    add depend type=incorporate [email protected]
                    close """,

                """
                    open [email protected],5.11-0
                    add depend type=incorporate [email protected]
                    close """,

                """
                    open [email protected],5.11-0
                    close """,
                )

        pub2_pkgs = (
                """
                    open [email protected],5.11-0
                    close """,
                )

        def setUp(self):
                pkg5unittest.ManyDepotTestCase.setUp(self, ["test1", "test2"])
                self.rurl = self.dcs[1].get_repo_url()
                self.rurl2 = self.dcs[2].get_repo_url()

                self.pkgsend_bulk(self.rurl, self.pkgs)
                self.pkgsend_bulk(self.rurl2, self.pub2_pkgs)

        def test_incorp_downgrade(self):
                """ Test that downgrades are allowed if they are incorporated
                by FMRIs which are requested by the user or are about to be
                updated as part of an update-all operation."""

                self.image_create(self.rurl, prefix="")
                self.pkg("install A incorp@1")
                self.pkg("list [email protected] [email protected]")

                # When incorp moves forward, A should move backwards
                self.pkg("update incorp@2")
                self.pkg("list [email protected] [email protected]")

                # start over and test if that also works if we do an update all
                self.pkg("update incorp@1")
                self.pkg("update -v")
                self.pkg("list [email protected] [email protected]")

                # prepare test for nested incorporations
                self.pkg("uninstall incorp")
                self.pkg("update A@2")
                self.pkg("install parent_incorp@1 child_incorp@2")
                self.pkg("list [email protected] child_incorp@2 parent_incorp@1")

                # test update of nested incorporation supports downgrade
                self.pkg("update -v parent_incorp@2")
                self.pkg("list A@1 child_incorp@1 parent_incorp@2")

                # prepare test for explicit downgrade of incorp
                self.pkg("uninstall parent_incorp")
                self.pkg("update child_incorp@2")
                self.pkg("list A@2 child_incorp@2")

                # test explicit downgrade of incorp downgrades incorp'ed pkgs
                self.pkg("update -v child_incorp@1")
                self.pkg("list A@1 child_incorp@1")


        def test_incorp_downgrade_installhold(self):
                """Test correct handling of install-holds when determining
                which pkgs are ok to downgrade."""

                # prepare test for install-hold
                self.image_create(self.rurl, prefix="")
                self.pkg("install ihold_incorp@2 B")
                self.pkg("list ihold_incorp@2 B@2")
                # test that install hold prevents downgrade
                self.pkg("update -v ihold_incorp@1", exit=1)

                # prep test for parent install-hold
                self.pkg("install --reject B C@2")
                self.pkg("list ihold_incorp@2 C@2")
                # test that downgrade is allowed if install-hold is child of
                # requested FMRI
                self.pkg("update -v ihold_incorp@1")
                self.pkg("list ihold_incorp@1 C@1")

        def test_incorp_downgrade_pubswitch(self):
                """Test that implicit publisher switches of incorporated pkgs
                are not allowed."""

                # prepare test for publisher switch
                self.image_create(self.rurl, prefix="")
                self.pkg("set-publisher --non-sticky test1")
                self.pkg("set-publisher --non-sticky -p {0}".format(self.rurl2))
                self.pkg("list -af")
                self.pkg("install p_incorp@1 D@2")
                self.pkg("list p_incorp@1 D@2")

                self.pkg("update p_incorp@2", exit=1)


if __name__ == "__main__":
        unittest.main()