add CLI test suite, correct bugs found by test suite, ration out CLI options
authorStephen Hahn <sch@Sun.COM>
Mon, 08 Oct 2007 13:18:41 -0700
changeset 135 a1e20e9a9845
parent 134 6887274c15a3
child 136 da65641c4607
add CLI test suite, correct bugs found by test suite, ration out CLI options
src/Makefile
src/depot.py
src/man/pkg.depotd.1m.txt
src/man/pkgsend.1.txt
src/modules/client/image.py
src/modules/client/imageplan.py
src/modules/client/pkgplan.py
src/modules/publish/transaction.py
src/modules/server/config.py
src/publish.py
src/tests/README
src/tests/cli-complete.ksh
--- a/src/Makefile	Fri Oct 05 17:12:55 2007 -0700
+++ b/src/Makefile	Mon Oct 08 13:18:41 2007 -0700
@@ -30,6 +30,7 @@
 
 MACH:sh = uname -p
 
+KSH=/usr/bin/ksh
 PYTHON = /usr/bin/python
 
 ROOT = ../proto/root_${MACH}
@@ -224,7 +225,7 @@
 	@cd web; pwd; $(MAKE) $(TARGET)
 	@cd brand; pwd; $(MAKE) $(TARGET)
 	@cd man; pwd; $(MAKE) $(TARGET)
-        
+
 # Invoke all known modules with tests.
 # XXX Invoke the bundle tests.
 test:
@@ -238,6 +239,7 @@
 	$(PYTHON) $(LINKPYTHONPKG)/smf.py
 	$(PYTHON) $(LINKPYTHONPKG)/client/imageconfig.py
 	$(PYTHON) $(LINKPYTHONPKG)/client/filter.py
+	cd tests; $(KSH) -x cli-complete.ksh
 
 proto: $(ROOT)
 
--- a/src/depot.py	Fri Oct 05 17:12:55 2007 -0700
+++ b/src/depot.py	Mon Oct 08 13:18:41 2007 -0700
@@ -68,8 +68,10 @@
 
 def usage():
         print """\
-Usage: /usr/lib/pkg.depotd [-n]
+Usage: /usr/lib/pkg.depotd [--readonly] [-d repo_dir] [-p port]
+        --readonly      Read-only operation; modifying operations disallowed
 """
+        sys.exit(2)
 
 def catalog_get(scfg, request):
         scfg.inc_catalog()
@@ -161,6 +163,10 @@
 def trans_open(scfg, request):
         # XXX Authentication will be handled by virtue of possessing a signed
         # certificate (or a more elaborate system).
+        if scfg.is_read_only():
+                request.send_error(403, "Read-only server")
+                return
+
         t = trans.Transaction()
 
         ret = t.open(scfg, request)
@@ -178,6 +184,10 @@
 
 
 def trans_close(scfg, request):
+        if scfg.is_read_only():
+                request.send_error(403, "Read-only server")
+                return
+
         # Pull transaction ID from headers.
         m = re.match("^/close/(.*)", request.path)
         trans_id = m.group(1)
@@ -188,6 +198,10 @@
         del scfg.in_flight_trans[trans_id]
 
 def trans_abandon(scfg, request):
+        if scfg.is_read_only():
+                request.send_error(403, "Read-only server")
+                return
+
         # Pull transaction ID from headers.
         m = re.match("^/abandon/(.*)", request.path)
         trans_id = m.group(1)
@@ -197,6 +211,10 @@
         del scfg.in_flight_trans[trans_id]
 
 def trans_add(scfg, request):
+        if scfg.is_read_only():
+                request.send_error(403, "Read-only server")
+                return
+
         m = re.match("^/add/([^/]*)/(.*)", request.path)
         trans_id = m.group(1)
         type = m.group(2)
@@ -263,22 +281,26 @@
         pass
 
 if __name__ == "__main__":
+        port = 10000
+
+        try:
+                opts, pargs = getopt.getopt(sys.argv[1:], "d:np:", ["readonly"])
+                for opt, arg in opts:
+                        if opt == "-n":
+                                sys.exit(0)
+                        elif opt == "-d":
+                                scfg.set_repo_root(arg)
+                        elif opt == "-p":
+                                port = int(arg)
+                        elif opt == "--readonly":
+                                scfg.set_read_only()
+        except getopt.GetoptError, e:
+                print "pkg.depotd: unknown option '%s'" % e.opt
+                usage()
+
         scfg.init_dirs()
         scfg.acquire_in_flight()
         scfg.acquire_catalog()
 
-        port = 10000
-
-        try:
-                opts, pargs = getopt.getopt(sys.argv[1:], "np:")
-                for opt, arg in opts:
-                        if opt == "-n":
-                                sys.exit(0)
-                        elif opt == "-p":
-                                port = int(arg)
-        except getopt.GetoptError, e:
-                print "pkg.depotd: unknown option '%s'" % e.opt
-                usage()
-
         server = ThreadingHTTPServer(('', port), pkgHandler)
         server.serve_forever()
--- a/src/man/pkg.depotd.1m.txt	Fri Oct 05 17:12:55 2007 -0700
+++ b/src/man/pkg.depotd.1m.txt	Mon Oct 08 13:18:41 2007 -0700
@@ -1,11 +1,11 @@
-System Administration Commands				 pkg.depotd(1M)
+System Administration Commands                           pkg.depotd(1M)
 
 
 NAME
      pkg.depotd - image packaging system depot server
 
 SYNOPSIS
-     /usr/lib/pkg.depotd
+     /usr/lib/pkg.depotd [--readonly] [-d repo_dir] [-p port]
 
 DESCRIPTION
      pkg.depotd is the depot server for the image packaging system.
@@ -20,40 +20,59 @@
      properties associated with its service instance.  Two properties
      are currently recognized
 
-     application/port		(count) The port number on which this
-				instance of should listen for incoming
-				package requests.  The default value is
-				80.
+     application/port           (count) The port number on which this
+                                instance of should listen for incoming
+                                package requests.  The default value is
+                                80.
+
+     application/read_only      (boolean) Modifying operations, such as
+                                those initiated by pkgsend(1M) are
+                                disabled.  Retrieval operations are
+                                still available.
 
-     application/repo_root	(astring) The file system path at which
-     				this instance should find its repository
-				data.  The default value is
-				/var/pkg/repo.
+     application/repo_dir       (astring) The file system path at which
+                                this instance should find its repository
+                                data.  The default value is
+                                /var/pkg/repo.
+
+OPTIONS
+     The following options alter the default behavior, if present, and
+     will override the settings from the service instance when managed
+     via an smf(5) restarter:
+
+     -d repo_dir                Overrides application/repo_dir with the
+                                value given by repo_dir.
+
+     -p port                    Overrides application/port with the
+                                value given by port.
+
+     --readonly                 Overrides application/read_only to be
+                                true.
 
 EXAMPLES
      Example 1:  Enabling the depot server.
 
      # svcadm enable application/pkg/server:default
-     
+
      Example 2:  Changing the listening port of the server.
 
      # svccfg -s application/pkg/server:default set application/port = 9999
      # svcadm refresh application/pkg/server:default
      # svcadm restart application/pkg/server:default
-     
+
 EXIT STATUS
      The following exit values are returned:
 
      0     Successful operation.
 
-     1     Error encountered.  
+     1     Error encountered.
 
      2     Invalid command line options were specified.
 
 FILES
-     /var/pkg/repo		Default repository location; modify
-     				application/repo_root to select an
-				alternate location.
+     /var/pkg/repo              Default repository location; modify
+                                application/repo_root to select an
+                                alternate location.
 
 ATTRIBUTES
      See attributes(5) for descriptions of the  following  attri-
@@ -71,7 +90,7 @@
      The image packaging system is an under-development feature.
      Command names, invocation, formats, and operations are all subject
      to change.  Development is hosted in the OpenSolaris community
-     at 
+     at
 
      http://opensolaris.org/os/project/pkg/
 
--- a/src/man/pkgsend.1.txt	Fri Oct 05 17:12:55 2007 -0700
+++ b/src/man/pkgsend.1.txt	Mon Oct 08 13:18:41 2007 -0700
@@ -22,6 +22,12 @@
      pkgsend also has an alternate invocation that allows the conversion
      of existing software bundles via a submission.
 
+OPTIONS
+     The following options alter the default behavior:
+
+     -s repo_url    The URL prefix of the depot server.  The default
+                    value is "http://localhost:10000/
+
 SUBCOMMANDS
      The following subcommands are supported:
 
--- a/src/modules/client/image.py	Fri Oct 05 17:12:55 2007 -0700
+++ b/src/modules/client/image.py	Mon Oct 08 13:18:41 2007 -0700
@@ -307,7 +307,7 @@
 
                 return "known"
 
-        def is_installed(self, fmri):
+        def has_version_installed(self, fmri):
                 """Check that the version given in the FMRI or a successor is
                 installed in the current image."""
 
@@ -321,6 +321,17 @@
 
                 return False
 
+        def is_installed(self, fmri):
+                """Check that the exact version given in the FMRI is installed
+                in the current image."""
+
+                try:
+                        v = self.get_version_installed(fmri)
+                except LookupError:
+                        return False
+
+                return v == fmri
+
         def get_dependents(self, pfmri):
                 """Return a list of the packages directly dependent on the given
                 FMRI."""
--- a/src/modules/client/imageplan.py	Fri Oct 05 17:12:55 2007 -0700
+++ b/src/modules/client/imageplan.py	Mon Oct 08 13:18:41 2007 -0700
@@ -113,7 +113,7 @@
 
         def propose_fmri(self, fmri):
                 # is a version of fmri.stem in the inventory?
-                if self.image.is_installed(fmri):
+                if self.image.has_version_installed(fmri):
                         return
 
                 #   is there a freeze or incorporation statement?
@@ -135,7 +135,7 @@
         # XXX Need to make sure that the same package isn't being added and
         # removed in the same imageplan.
         def propose_fmri_removal(self, fmri):
-                if not self.image.is_installed(fmri):
+                if not self.image.has_version_installed(fmri):
                         return
 
                 for i, p in enumerate(self.target_rem_fmris):
@@ -144,11 +144,9 @@
                                         self.target_rem_fmris[i] = fmri
                                         break
                 else:
-                        if "i" not in locals():
-                                self.target_rem_fmris.append(fmri)
+                        self.target_rem_fmris.append(fmri)
 
         def evaluate_fmri(self, pfmri):
-
                 # [image] do we have this manifest?
                 if not self.image.has_manifest(pfmri):
                         retrieve.get_manifest(self.image, pfmri)
@@ -163,7 +161,7 @@
                         f = fmri.PkgFmri(a.attrs["fmri"],
                             self.image.attrs["Build-Release"])
 
-                        if self.image.is_installed(f):
+                        if self.image.has_version_installed(f):
                                 continue
 
                         # XXX This alone only prevents infinite recursion when a
@@ -172,7 +170,8 @@
                         # what was specified on the commandline, or include what
                         # we've found while processing dependencies?
                         # XXX probably should just use propose_fmri() here
-                        # instead of this and the is_installed() call above.
+                        # instead of this and the has_version_installed() call
+                        # above.
                         if self.is_proposed_fmri(f):
                                 continue
 
@@ -194,13 +193,15 @@
                         if excluded:
                                 raise RuntimeError, "excluded by '%s'" % f
 
-                        # treat-as-required, treat-as-required-unless-pinned, ignore
+                        # treat-as-required, treat-as-required-unless-pinned,
+                        # ignore
                         # skip if ignoring
                         #     if pinned
                         #       ignore if treat-as-required-unless-pinned
                         #     else
                         #       **evaluation of incorporations**
-                        #     [imageplan] pursue installation of this package -->
+                        #     [imageplan] pursue installation of this package
+                        #     -->
                         #     backtrack or reset??
 
                         mvs = self.image.get_matching_pkgs(f)
@@ -231,9 +232,18 @@
 
                 dependents = self.image.get_dependents(pfmri)
 
+                # Don't consider those dependencies already being removed in
+                # this imageplan transaction.
+                for i, d in enumerate(dependents):
+                        if fmri.PkgFmri(d, None) in self.target_rem_fmris:
+                                del dependents[i]
+
                 if dependents and not self.recursive_removal:
-                        print "Cannot remove '%s' due to" % pfmri
-                        print "the following packages that directly depend on it:"
+                        # XXX Module function is printing, should raise or have
+                        # complex return.
+                        print """\
+Cannot remove '%s' due to the following packages that directly depend on it:"""\
+                        % pfmri
                         for d in dependents:
                                 print " ", fmri.PkgFmri(d, "")
                         return
@@ -243,6 +253,7 @@
                 pp = pkgplan.PkgPlan(self.image)
 
                 try:
+                        print "pkgplan remove %s proposed" % pfmri
                         pp.propose_removal(pfmri, m)
                 except RuntimeError:
                         print "pkg %s not installed" % pfmri
@@ -253,8 +264,10 @@
                 for d in dependents:
                         rf = fmri.PkgFmri(d, None)
                         if self.is_proposed_rem_fmri(rf):
+                                print "%s is already proposed for removal" % rf
                                 continue
-                        if not self.image.is_installed(rf):
+                        if not self.image.has_version_installed(rf):
+                                print "%s is not installed" % rf
                                 continue
                         self.target_rem_fmris.append(rf)
                         self.evaluate_fmri_removal(rf)
--- a/src/modules/client/pkgplan.py	Fri Oct 05 17:12:55 2007 -0700
+++ b/src/modules/client/pkgplan.py	Mon Oct 08 13:18:41 2007 -0700
@@ -33,7 +33,7 @@
         """A package plan takes two package FMRIs and an Image, and produces the
         set of actions required to take the Image from the origin FMRI to the
         destination FMRI.
-        
+
         If the destination FMRI is None, the package is removed.
         """
 
@@ -117,7 +117,7 @@
                                 f = file("%s/pkg/%s/filters" % \
                                     (self.image.imgdir,
                                     self.origin_fmri.get_dir_path()), "r")
-                        except OSError, e:
+                        except IOError, e:
                                 if e.errno != errno.ENOENT:
                                         raise
                         else:
@@ -141,7 +141,7 @@
 
         def preexecute(self):
                 """Perform actions required prior to installation or removal of a package.
-                
+
                 This method executes each action's preremove() or preinstall()
                 methods, as well as any package-wide steps that need to be taken
                 at such a time.
@@ -154,8 +154,13 @@
                         os.unlink("%s/pkg/%s/installed" % (self.image.imgdir,
                             self.origin_fmri.get_dir_path()))
 
-                        os.unlink("%s/pkg/%s/filters" % (self.image.imgdir,
-                            self.origin_fmri.get_dir_path()))
+                        try:
+                                os.unlink("%s/pkg/%s/filters" % (
+                                    self.image.imgdir,
+                                    self.origin_fmri.get_dir_path()))
+                        except OSError, e:
+                                if e.errno != errno.ENOENT:
+                                        raise
 
                 for src, dest in self.actions:
                         if dest:
@@ -193,7 +198,7 @@
 
         def execute(self):
                 """Perform actions for installation or removal of a package.
-                
+
                 This method executes each action's remove() or install()
                 methods.
                 """
@@ -219,7 +224,7 @@
 
         def postexecute(self):
                 """Perform actions required after installation or removal of a package.
-                
+
                 This method executes each action's postremove() or postinstall()
                 methods, as well as any package-wide steps that need to be taken
                 at such a time.
@@ -238,8 +243,13 @@
                         os.unlink("%s/pkg/%s/installed" % (self.image.imgdir,
                             self.origin_fmri.get_dir_path()))
 
-                        os.unlink("%s/pkg/%s/filters" % (self.image.imgdir,
-                            self.origin_fmri.get_dir_path()))
+                        try:
+                                os.unlink("%s/pkg/%s/filters" % (
+                                    self.image.imgdir,
+                                    self.origin_fmri.get_dir_path()))
+                        except OSError, e:
+                                if e.errno != errno.ENOENT:
+                                        raise
 
                 if self.destination_fmri != None:
                         file("%s/pkg/%s/installed" % (self.image.imgdir,
@@ -260,7 +270,7 @@
 
         def make_indices(self):
                 """Create the reverse index databases for a particular package.
-                
+
                 These are the databases mapping packaging object attribute
                 values back to their corresponding packages, allowing the
                 packaging system to look up a package based on, say, the
--- a/src/modules/publish/transaction.py	Fri Oct 05 17:12:55 2007 -0700
+++ b/src/modules/publish/transaction.py	Mon Oct 08 13:18:41 2007 -0700
@@ -168,7 +168,7 @@
                 try:
                         r = c.getresponse()
                 except httplib.BadStatusLine:
-                        print "Server-side exception for:", action
+                        print "Server-side exception for '%s'" % action
                         return 500
 
                 return r.status
--- a/src/modules/server/config.py	Fri Oct 05 17:12:55 2007 -0700
+++ b/src/modules/server/config.py	Mon Oct 08 13:18:41 2007 -0700
@@ -25,7 +25,9 @@
 # Use is subject to license terms.
 #
 
+import errno
 import os
+import statvfs
 import urllib
 
 import pkg.catalog as catalog
@@ -34,6 +36,11 @@
 
 import pkg.server.transaction as trans
 
+# OpenSolaris:  statvfs(2) flags field
+ST_RDONLY = 0x01
+ST_NOSUID = 0x02
+ST_NOTRUNC = 0x04
+
 # depot Server Configuration
 
 class SvrConfig(object):
@@ -43,13 +50,12 @@
         transactions and packages stored by the repository."""
 
         def __init__(self, repo_root, authority):
-                self.repo_root = repo_root
-                self.trans_root = "%s/trans" % self.repo_root
-                self.file_root = "%s/file" % self.repo_root
-                self.pkg_root = "%s/pkg" % self.repo_root
+                self.set_repo_root(repo_root)
 
                 self.authority = authority
 
+                self.read_only = False
+
                 self.catalog = catalog.Catalog()
                 self.in_flight_trans = {}
 
@@ -64,13 +70,52 @@
                 self.flist_files = 0
 
         def init_dirs(self):
-                # XXX refine try/except
+                root_needed = False
+
                 try:
+                        vfs = os.statvfs(self.repo_root)
+                except OSError, e:
+                        if e.errno == errno.ENOENT:
+                                root_needed = True
+                        else:
+                                raise
+
+                if root_needed:
+                        try:
+                                os.makedirs(self.repo_root)
+                        except OSError, e:
+                                raise
+
+                        vfs = os.statvfs(self.repo_root)
+
+                emsg = "repository directories incomplete"
+
+                if vfs[statvfs.F_FLAG] & ST_RDONLY != 0:
+                        self.set_read_only()
+                        emsg = "repository directories read-only and incomplete"
+                else:
                         os.makedirs(self.trans_root)
                         os.makedirs(self.file_root)
                         os.makedirs(self.pkg_root)
-                except OSError:
-                        pass
+
+                if os.path.exists(self.trans_root) and \
+                    os.path.exists(self.file_root) and \
+                    os.path.exists(self.pkg_root):
+                        return
+
+                raise RuntimeError, emsg
+
+        def set_repo_root(self, root):
+                self.repo_root = root
+                self.trans_root = "%s/trans" % self.repo_root
+                self.file_root = "%s/file" % self.repo_root
+                self.pkg_root = "%s/pkg" % self.repo_root
+
+        def set_read_only(self):
+                self.read_only = True
+
+        def is_read_only(self):
+                return self.read_only
 
         def acquire_in_flight(self):
                 """Walk trans_root, acquiring valid transaction IDs."""
--- a/src/publish.py	Fri Oct 05 17:12:55 2007 -0700
+++ b/src/publish.py	Mon Oct 08 13:18:41 2007 -0700
@@ -40,8 +40,10 @@
 #       pkgsend close -A
 
 import getopt
+import gettext
 import os
 import sys
+import threading
 import traceback
 
 import pkg.bundle
@@ -54,10 +56,9 @@
 import pkg.publish.transaction as trans
 
 import pkg.Queue25 as Queue25
-import threading
 
 def usage():
-        print """\
+        print _("""\
 Usage:
         pkgsend [options] command [cmd_options] [operands]
 
@@ -74,10 +75,10 @@
         pkgsend close [-A]
 
 Options:
-        --repo, -s
+        -s repo_url     destination repository server URL prefix
 
 Environment:
-        PKG_REPO"""
+        PKG_REPO""")
         sys.exit(2)
 
 def trans_open(config, args):
@@ -86,7 +87,7 @@
         try:
                 opts, pargs = getopt.getopt(args, "e")
         except getopt.GetoptError, e:
-                print "pkgsend: illegal open option '%s'" % e.opt
+                print _("pkgsend: illegal open option '%s'") % e.opt
                 usage()
 
         eval_form = True
@@ -97,7 +98,7 @@
                         eval_form = False
 
         if len(pargs) != 1:
-                print "pkgsend: open requires one package name"
+                print _("pkgsend: open requires one package name")
                 usage()
 
         t = trans.Transaction()
@@ -105,11 +106,11 @@
         status, id = t.open(config, pargs[0])
 
         if status / 100 == 4 or status / 100 == 5:
-                print "pkgsend: server failed (status %s)" % status
+                print _("pkgsend: server failed (status %s)") % status
                 sys.exit(1)
 
         if id == None:
-                print "pkgsend: no transaction ID provided in response"
+                print _("pkgsend: no transaction ID provided in response")
                 sys.exit(1)
 
         if eval_form:
@@ -135,7 +136,7 @@
                                 if opt == "-t":
                                         trans_id = arg
         except getopt.GetoptError, e:
-                print "pkgsend: illegal option to close '%s'" % e.opt
+                print _("pkgsend: illegal option to close: '%s'") % e.opt
                 usage()
 
         if trans_id == None:
@@ -157,8 +158,6 @@
         else:
                 print "Failed with", ret
 
-        return
-
 def trans_add(config, args):
         try:
                 trans_id = os.environ["PKG_TRANS_ID"]
@@ -187,7 +186,7 @@
         """Via POST request, transfer a piece of metadata to the server."""
 
         if not args[0] in ["set", "unset"]:
-                print "pkgsend: unknown metadata item '%s'" % args[0]
+                print _("pkgsend: unknown metadata item '%s'") % args[0]
                 usage()
 
         trans_id = os.environ["PKG_TRANS_ID"]
@@ -197,9 +196,6 @@
 
         return
 
-def trans_summary(config, args):
-        return
-
 def batch(config, args):
         return
 
@@ -273,21 +269,32 @@
 
         t.close(config, id)
 
-
-pcfg = config.ParentRepo("http://localhost:10000", ["http://localhost:10000"])
+try:
+        repo = os.environ["PKG_REPO"]
+except KeyError:
+        repo_url = "http://localhost:10000"
 
 if __name__ == "__main__":
+        # XXX /usr/lib/locale is OpenSolaris-specific.
+        gettext.install("pkgsend", "/usr/lib/locale")
+
         opts = None
         pargs = None
         try:
-                opts, pargs = getopt.getopt(sys.argv[1:], "s:R:")
+                opts, pargs = getopt.getopt(sys.argv[1:], "s:")
+                for opt, arg in opts:
+                        if opt == "-s":
+                                repo_url = arg
+
         except getopt.GetoptError, e:
-                print "pkgsend: illegal global option '%s'" % e.opt
+                print _("pkgsend: illegal global option '%s'") % e.opt
                 usage()
 
         if pargs == None or len(pargs) == 0:
                 usage()
 
+        pcfg = config.ParentRepo(repo_url, [repo_url])
+
         subcommand = pargs[0]
         del pargs[0]
 
@@ -300,7 +307,7 @@
         elif subcommand == "send":
                 send_bundles(pcfg, pargs)
         else:
-                print "pkgsend: unknown subcommand '%s'" % subcommand
+                print _("pkgsend: unknown subcommand '%s'") % subcommand
                 usage()
 
         sys.exit(0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tests/README	Mon Oct 08 13:18:41 2007 -0700
@@ -0,0 +1,38 @@
+
+Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+Use is subject to license terms.
+
+tests/README
+
+  1.  Summary
+
+  The global test target, i.e. "make test", should be executed prior to
+  requesting code review and prior to integration.  That target invokes
+  both the unit tests embedded in a subset of the modules and the
+  tests/cli-complete.ksh script.  The remainder of tests/ is a
+  miscellany of ksh scripts that do various small dependency scenarios.
+
+  cli-complete.ksh is a script that exercises each of the command line
+  options of pkg(1) and pkgsend(1), using a variety of dependency
+  scenarios.  
+
+  2.  Pending work.
+
+  The embedded tests from the various modules should become disembedded
+  and coded to use the Python unittest testing framework module.
+
+  The special dependency scenarios should be recoded as part of
+  cli-compelete.ksh
+
+  Targets to evaluate code coverage [1,2] should be added, possibly with
+  supporting global options added to each utility.
+
+  3.  References
+
+  [1] N. Batchelder, coverage module, 2007.  
+      http://nedbatchelder.com/code/modules/coverage.html
+
+  [2] C. T. Brown, figleaf module, 2006.
+      http://darcs.idyll.org/~t/projects/figleaf/README.html
+
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tests/cli-complete.ksh	Mon Oct 08 13:18:41 2007 -0700
@@ -0,0 +1,338 @@
+#!/bin/ksh -px
+#
+# 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 2007 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+
+# cli-complete.ksh - basic sanity test exercising all basic pkg(1) operations
+#
+# cli-complete.ksh runs out of $HG/src/tests, and runs an depot server
+# and issues pkg(1) commands from the $HG/proto directory relevant to
+# the executing system.
+#
+# XXX Should really select which cases to run.
+
+REPO_PORT=${REPO_PORT:-8000}
+REPO_DIR=/tmp/repo.$$
+REPO_URL=http://localhost:$REPO_PORT
+IMAGE_DIR=/tmp/image.$$
+
+restore_dir=$PWD
+
+ROOT=$PWD/../../proto/root_$(uname -p)
+
+export PYTHONPATH=$ROOT/usr/lib/python2.4/vendor-packages/
+export PATH=$ROOT/usr/bin:$PATH
+
+$ROOT/usr/lib/pkg.depotd -p $REPO_PORT -d $REPO_DIR &
+
+DEPOT_PID=$!
+
+sleep 1
+
+usage () {
+	cli-complete.ksh
+	exit 2
+}
+
+depot_cleanup () {
+	kill $DEPOT_PID
+	sleep 1
+	if ps -p $DEPOT_PID > /dev/null; then
+		kill -9 $DEPOT_PID
+	fi
+	rm -fr $REPO_DIR
+	exit $1
+}
+
+image_cleanup () {
+	cd $restore_dir
+	rm -fr $IMAGE_DIR
+}
+
+fail () {
+	echo "*** case $tcase: $@"
+	exit 1
+}
+
+trap "ret=$?; image_cleanup; depot_cleanup $ret" EXIT
+
+# Case 1.  Send empty package [email protected], install and uninstall.
+# {{{1
+
+tcase=1
+trans_id=$(pkgsend -s $REPO_URL open [email protected],5.11-0)
+if [[ $? != 0 ]]; then
+	fail pkgsend open failed
+fi
+
+eval $trans_id
+
+if ! pkgsend -s $REPO_URL close; then
+	fail pkgsend close failed
+fi
+
+if ! pkg image-create -F -a test=$REPO_URL $IMAGE_DIR; then
+	fail pkg image-create failed
+fi
+
+find $IMAGE_DIR
+
+cd $IMAGE_DIR
+
+if ! pkg status -a; then
+	fail pkg status -a failed
+fi
+
+if ! pkg install foo; then
+	fail pkg install foo failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg status; then
+	fail pkg status failed
+fi
+
+if ! pkg uninstall foo; then
+	fail pkg uninstall foo failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg status -a; then
+	fail pkg status -a failed
+fi
+
+# }}}1
+
+# Case 2.  Send package [email protected], containing a directory and a file,
+# install and uninstall.
+# {{{1
+
+tcase=2
+trans_id=$(pkgsend -s $REPO_URL open [email protected],5.11-0)
+if [[ $? != 0 ]]; then
+	fail pkgsend open failed
+fi
+
+eval $trans_id
+
+if ! pkgsend -s $REPO_URL add dir mode=0755 owner=root group=bin path=/lib; then
+	fail pkgsend add dir failed
+fi
+
+if ! pkgsend -s $REPO_URL add file /lib/libc.so.1 mode=0555 owner=root group=bin \
+	path=/lib/libc.so.1; then
+	fail pkgsend add file failed
+fi
+
+if ! pkgsend -s $REPO_URL close; then
+	fail pkgsend close failed
+fi
+
+if ! pkg image-create -F -a test=$REPO_URL $IMAGE_DIR; then
+	fail pkg image-create failed
+	image_cleanup
+fi
+
+find $IMAGE_DIR
+
+cd $IMAGE_DIR
+
+if ! pkg status -a; then
+	fail pkg status -a failed
+fi
+
+if ! pkg install foo; then
+	fail pkg install foo failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg status; then
+	fail pkg status failed
+fi
+
+if ! pkg uninstall foo; then
+	fail pkg uninstall foo failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg status -a; then
+	fail pkg status -a failed
+fi
+
+# }}}1
+
+# Case 3.  Install [email protected], upgrade to [email protected], uninstall.
+# {{{1
+
+tcase=3
+
+if ! pkg image-create -F -a test=$REPO_URL $IMAGE_DIR; then
+	fail pkgsend close failed
+fi
+
+find $IMAGE_DIR
+
+cd $IMAGE_DIR
+
+if ! pkg status -a; then
+	fail pkg status -a failed
+fi
+
+if ! pkg install [email protected]; then
+	fail pkg install foo failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg status; then
+	fail pkg status failed
+fi
+
+if ! pkg install [email protected]; then
+	fail pkg install foo \(1.0 -\> 1.1\) failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg uninstall foo; then
+	fail pkg status failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg status -a; then
+	fail pkg status -a failed
+fi
+
+# }}}1
+
+# Case 4.  Add [email protected], dependent on [email protected], install, uninstall.
+# {{{1
+
+tcase=4
+trans_id=$(pkgsend -s $REPO_URL open [email protected],5.11-0)
+if [[ $? != 0 ]]; then
+	fail pkgsend open failed
+fi
+
+eval $trans_id
+
+if ! pkgsend -s $REPO_URL add depend type=require fmri=pkg:/[email protected]; then
+	fail pkgsend add depend require failed
+fi
+
+if ! pkgsend -s $REPO_URL add dir mode=0755 owner=root group=bin path=/bin; then
+	fail pkgsend add dir failed
+fi
+
+if ! pkgsend -s $REPO_URL add file /bin/cat mode=0555 owner=root group=bin \
+	path=/bin/cat; then
+	fail pkgsend add file failed
+fi
+
+if ! pkgsend -s $REPO_URL close; then
+	fail pkgsend close failed
+fi
+
+if ! pkg image-create -F -a test=$REPO_URL $IMAGE_DIR; then
+	fail pkgsend close failed
+fi
+
+find $IMAGE_DIR
+
+cd $IMAGE_DIR
+
+if ! pkg status -a; then
+	fail pkg status -a failed
+fi
+
+if ! pkg install [email protected]; then
+	fail pkg install bar failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg status; then
+	fail pkg status failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg uninstall -v bar foo; then
+	fail pkg uninstall failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg status -a; then
+	fail pkg status -a failed
+fi
+
+# }}}1
+
+# Case 5.  Install [email protected], dependent on [email protected], uninstall recursively.
+# {{{1
+
+tcase=5
+
+if ! pkg image-create -F -a test=$REPO_URL $IMAGE_DIR; then
+	fail pkg image-create failed
+fi
+
+find $IMAGE_DIR
+
+cd $IMAGE_DIR
+
+if ! pkg status -a; then
+	fail pkg status -a failed
+fi
+
+if ! pkg install [email protected]; then
+	fail pkg install bar failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg status; then
+	fail pkg status failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg uninstall -vr bar; then
+	fail pkg uninstall -vr failed
+fi
+
+find $IMAGE_DIR
+
+if ! pkg status -a; then
+	fail pkg status -a failed
+fi
+
+# }}}1
+
+exit 0