17710 matching version class parsing fails if build version is omitted
authorShawn Walker <shawn.walker@oracle.com>
Wed, 26 Jan 2011 19:03:43 -0800
changeset 2199 7ddd3f2cfb0b
parent 2198 6d551c326f54
child 2200 155eda704b2f
17710 matching version class parsing fails if build version is omitted 16624 pkg(5) should document that leading zeros are illegal in fmris 17711 pkg(5) fmri syntax vague and missing information about optional components
src/man/pkg.5.txt
src/modules/version.py
src/tests/api/t_api_list.py
src/tests/api/t_version.py
src/tests/cli/t_pkg_install.py
--- a/src/man/pkg.5.txt	Wed Jan 26 15:14:08 2011 -0800
+++ b/src/man/pkg.5.txt	Wed Jan 26 19:03:43 2011 -0800
@@ -24,6 +24,17 @@
      'opensolaris.org' is the publisher.  'library/libc' is the package
      name.  Although the namespace is hierarchical and arbitrarily deep,
      there is no enforced containment -- the name is essentially arbitrary.
+     The publisher information is optional, but must be preceded by 'pkg://'
+     if present.  An FMRI that includes the publisher is often referred to as
+     being "fully qualified".  If publisher information is not present, then
+     the package name should generally be preceded by 'pkg:/'.
+
+     Packaging clients often allow the scheme of an FMRI to be omitted if it
+     does not contain publisher information.  For example, 'pkg:/library/libc'
+     may be written as 'library/libc'.  If the scheme is omitted, clients also
+     allow omission of all but the last component of a package name for matching
+     purposes.  For example, 'library/libc' could be written as 'libc', which
+     would then match packages named 'libc' or package names ending in '/libc'.
 
      A publisher's identifying name is a forward domain name that can be used
      to identify a person, group of persons, or an organization as the source
@@ -35,32 +46,36 @@
      The version follows the package name, separated by an '@'.  It
      consists of four sequences of numbers, separated by punctuation.  The
      elements in the first three sequences are separated by dots, and the
-     sequences are arbitrarily long.
+     sequences are arbitrarily long.  Leading zeros in version components
+     (e.g. 01.1 or 1.01) are not permitted, although trailing zeros are
+     (e.g. 1.10).
 
      The first part is the component version.  For components tightly bound
-     to OpenSolaris, this will usually be the value of 'uname -r' for that
-     version of OpenSolaris.  For a component with its own development
-     lifecycle, this sequence will be the dotted release number, such as
-     '2.4.10'.
+     to the operating system, this will usually be the value of 'uname -r'
+     for that version of the operating system.  For a component with its own
+     development lifecycle, this sequence will be the dotted release number,
+     such as '2.4.10'.
 
-     The second part, following the comma, is the build version, specifying
-     what version of OpenSolaris the contents of the package were built on,
-     providing a minimum bound on which OpenSolaris version the contents can be
-     expected to run successfully.
+     The second part, which if present must follow a comma, is the build
+     version, specifying what version of the operating system the contents
+     of the package were built on, providing a minimum bound on which
+     operating system version the contents can be expected to run
+     successfully.
 
-     The third part, following the dash, is the branch version, a
-     versioning component, providing vendor-specific information.  This may
-     be incremented when the packaging metadata is changed, independently
-     of the component, may contain a build number, or some other
-     information.
+     The third part, which if present must follow a dash, is the branch
+     version, a versioning component, providing vendor-specific information.
+     This may be incremented when the packaging metadata is changed,
+     independently of the component, may contain a build number, or some
+     other information.
 
-     The fourth part, following the colon, is a timestamp.  It represents
-     when the package was published.
+     The fourth part, which if present must follow a colon, is a timestamp.
+     It represents when the package was published.
 
      Many parts of the system, when appropriate, will contract FMRIs when
-     displaying them to reduce the volume of information provided.
-     Typically, the scheme, publisher, build version, and timestamp will be
-     elided, and sometimes the versioning information altogether.
+     displaying them, and accept input in shorter forms to reduce the volume
+     of information displayed or required.  Typically, the scheme, publisher,
+     build version, and timestamp can be elided, and sometimes the versioning
+     information altogether.
 
   Actions
      Actions represent the installable objects on a system.  They are
@@ -227,27 +242,28 @@
            become constrained.  See 'Constraints and Freezing' below.
 
            If the value is 'require-any', then any one of multiple dependent
-	   packages as specified by multiple 'fmri' attributes will satisfy 
+           packages as specified by multiple 'fmri' attributes will satisfy
            the dependency
 
-	   If the value is 'conditional', the dependency is required
-	   only if the package defined by the predicate attribute is present
-	   on the system.
+           If the value is 'conditional', the dependency is required
+           only if the package defined by the predicate attribute is present
+           on the system.
 
-	   If the value is 'origin', the dependency must, if present,
-	   be at the specified value or better on the image to be modified
-           prior to installation.  If the value of the 'root-image' attribute 
-           is 'true', the dependency must be present on the image rooted at '/' 
+           If the value is 'origin', the dependency must, if present,
+           be at the specified value or better on the image to be modified
+           prior to installation.  If the value of the 'root-image' attribute
+           is 'true', the dependency must be present on the image rooted at '/'
            in order to install this package.
 
-     fmri  The FMRI representing the depended-upon package.  In the case
-           of require-any dependencies, there may be multiple values.  This
-	   is the dependency action's key attribute.
+     fmri  The FMRI representing the depended-upon package.  It must not
+           include the publisher.  In the case of require-any dependencies,
+           there may be multiple values.  This is the dependency action's
+           key attribute.
 
      predicate  The FMRI representing the predicate for 'conditional'
-           dependencies
+           dependencies.
 
-     root-image Has an effect only for origin dependencies as mentioned 
+     root-image Has an effect only for origin dependencies as mentioned
            above.
 
   License actions
--- a/src/modules/version.py	Wed Jan 26 15:14:08 2011 -0800
+++ b/src/modules/version.py	Wed Jan 26 19:03:43 2011 -0800
@@ -21,8 +21,7 @@
 #
 
 #
-# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
-# Use is subject to license terms.
+# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
 #
 
 import calendar
@@ -548,7 +547,7 @@
 
         @classmethod
         def split(self, ver):
-                """Takes an assumed valid version string and asplits it into
+                """Takes an assumed valid version string and splits it into
                 its components as a tuple of the form ((release, build_release,
                 branch, timestr), short_ver)."""
 
@@ -599,27 +598,11 @@
                 if version_string is None or not len(version_string):
                         raise IllegalVersion, "Version cannot be empty"
 
-                release = None
-                build_release = None
-                branch = None
-                timestr = None
-                try:
-                        release, rem = version_string.split(",")
 
-                except ValueError:
-                        release = version_string
-                else:
-                        try:
-                                build_release, rem = rem.split("-")
-                        except ValueError:
-                                build_release = rem
-                        else:
-                                try:
-                                        branch, rem = rem.split(":")
-                                except (TypeError, ValueError):
-                                        branch = rem
-                                else:
-                                        timestr = rem
+                (release, build_release, branch, timestr), ignored = \
+                    self.split(version_string)
+                if not build_release:
+                        build_release = build_string
 
                 #
                 # Error checking and conversion from strings to objects
@@ -632,12 +615,11 @@
                         #
                         for attr, vals in (
                             ('release', (release,)),
-                            ('build_release', (build_release, build_string,
-                            "*")),
+                            ('build_release', (build_release, "*")),
                             ('branch', (branch, "*")),
                             ('timestr', (timestr, "*"))):
                                 for val in vals:
-                                        if val is None:
+                                        if not val:
                                                 continue
                                         if attr != 'timestr':
                                                 val = MatchingDotSequence(val)
--- a/src/tests/api/t_api_list.py	Wed Jan 26 15:14:08 2011 -0800
+++ b/src/tests/api/t_api_list.py	Wed Jan 26 19:03:43 2011 -0800
@@ -1461,7 +1461,7 @@
 
                 # Finally, verify that specifying an illegal pattern will
                 # raise an InventoryException.
-                patterns = ["baz@1.*.a", "baz@*-1"]
+                patterns = ["baz@1.*.a"]
                 expected = [
                     version.IllegalVersion(
                         "Bad Version: %s" % p.split("@", 1)[-1])
@@ -1471,7 +1471,7 @@
                         returned = self.__get_returned(api_obj.LIST_ALL,
                             api_obj=api_obj, patterns=patterns, variants=True)
                 except api_errors.InventoryException, e:
-                        self.assertEqualDiff(e.illegal, expected)
+                        self.assertEqualDiff(expected, e.illegal)
                 else:
                         raise RuntimeError("InventoryException not raised!")
 
--- a/src/tests/api/t_version.py	Wed Jan 26 15:14:08 2011 -0800
+++ b/src/tests/api/t_version.py	Wed Jan 26 19:03:43 2011 -0800
@@ -20,8 +20,9 @@
 # CDDL HEADER END
 #
 
-# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
-# Use is subject to license terms.
+#
+# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+#
 
 import testutils
 if __name__ == "__main__":
@@ -349,5 +350,42 @@
                 self.v1.set_timestamp(d)
                 self.assert_(self.v1.get_timestamp() == d)
 
+        def testsplit(self):
+                """Verify that split() works as expected."""
+
+                sver = "1.0,5.11-0.156:20101231T161351Z"
+                expected = (("1.0", "5.11", "0.156", "20101231T161351Z"),
+                    "1.0-0.156")
+                self.assertEqualDiff(expected, version.Version.split(sver)) 
+
+                sver = "1.0:20101231T161351Z"
+                expected = (("1.0", "", None, "20101231T161351Z"), "1.0")
+                self.assertEqualDiff(expected, version.Version.split(sver)) 
+
+                sver = ":20101231T161351Z"
+                expected = (("", "", None, "20101231T161351Z"), "")
+                self.assertEqualDiff(expected, version.Version.split(sver)) 
+
+                sver = "1.0,5.11-0.156"
+                expected = (("1.0", "5.11", "0.156", None), "1.0-0.156")
+                self.assertEqualDiff(expected, version.Version.split(sver)) 
+
+                sver = "-0.156"
+                expected = (("", "", "0.156", None), "-0.156")
+                self.assertEqualDiff(expected, version.Version.split(sver)) 
+
+                sver = "1.0,5.11"
+                expected = (("1.0", "5.11", None, None), "1.0")
+                self.assertEqualDiff(expected, version.Version.split(sver)) 
+
+                sver = ",5.11"
+                expected = (("", "5.11", None, None), "")
+                self.assertEqualDiff(expected, version.Version.split(sver)) 
+
+                sver = "1.0"
+                expected = (("1.0", "", None, None), "1.0")
+                self.assertEqualDiff(expected, version.Version.split(sver)) 
+
+
 if __name__ == "__main__":
         unittest.main()
--- a/src/tests/cli/t_pkg_install.py	Wed Jan 26 15:14:08 2011 -0800
+++ b/src/tests/cli/t_pkg_install.py	Wed Jan 26 19:03:43 2011 -0800
@@ -847,7 +847,9 @@
                 # 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")
-                self.pkg("update -v --reject 'idr1*' [email protected],5.11-0.2")
+                # 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")