Repository: kudu
Updated Branches:
  refs/heads/master 34b058b8b -> 8e40dfdb9


[util] update on ParseVersion()

This changelist updates implementation of the kudu::ParseVersion()
utility function to recognize and successfully parse version strings
like '1.8.0-cdh6.x-SNAPSHOT'.

Version::ToString() implementation changed to return
'canonical' representation of the parsed version string.  Added
corresponding unit tests.

This patch also fixes KUDU-2511.

This is a follow-up for c12e63528a2f2c265136e702a48cbc6c93b7f2fd.

Change-Id: I98b200d0529cb4471e4e855b88bfdb2ff2f346bf
Reviewed-on: http://gerrit.cloudera.org:8080/11060
Tested-by: Alexey Serbin <aser...@cloudera.com>
Reviewed-by: Andrew Wong <aw...@cloudera.com>


Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/7e733a73
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/7e733a73
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/7e733a73

Branch: refs/heads/master
Commit: 7e733a73652db68342859d03d02763ef5e3de79d
Parents: 34b058b
Author: Alexey Serbin <aser...@cloudera.com>
Authored: Wed Jul 25 22:07:11 2018 -0700
Committer: Alexey Serbin <aser...@cloudera.com>
Committed: Mon Jul 30 01:55:52 2018 +0000

----------------------------------------------------------------------
 src/kudu/util/version_util-test.cc | 88 +++++++++++++++++++++++++++------
 src/kudu/util/version_util.cc      | 29 ++++++-----
 src/kudu/util/version_util.h       | 11 +++--
 3 files changed, 98 insertions(+), 30 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/7e733a73/src/kudu/util/version_util-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/util/version_util-test.cc 
b/src/kudu/util/version_util-test.cc
index 54e8e76..3cb39b9 100644
--- a/src/kudu/util/version_util-test.cc
+++ b/src/kudu/util/version_util-test.cc
@@ -14,53 +14,109 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+
 #include "kudu/util/version_util.h"
 
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <gtest/gtest.h>
 
 #include "kudu/util/status.h"
 #include "kudu/util/test_macros.h"
-#include "kudu/util/test_util.h"
+#include "kudu/util/version_info.h"
 
 using std::string;
+using std::pair;
 using std::vector;
 
 namespace kudu {
 
-class VersionUtilTest : public KuduTest {};
-
-TEST_F(VersionUtilTest, TestVersion) {
-  const vector<Version> good_test_cases = {
-    { "0.0.0", 0, 0, 0, "" },
-    { "1.0.0", 1, 0, 0, "" },
-    { "1.1.0", 1, 1, 0, "" },
-    { "1.1.1", 1, 1, 1, "" },
-    { "1.10.100-1000", 1, 10, 100, "1000" },
-    { "1.2.3-SNAPSHOT", 1, 2, 3, "SNAPSHOT" },
+TEST(VersionUtilTest, TestVersion) {
+  const vector<pair<Version, string>> good_test_cases = {
+    { { "0.0.0", 0, 0, 0, "" }, "0.0.0" },
+    { { "1.0.0", 1, 0, 0, "" }, "1.0.0" },
+    { { "1.1.0", 1, 1, 0, "" }, "1.1.0" },
+    { { "1.1.1", 1, 1, 1, "" }, "1.1.1" },
+    { { "1.1.1-", 1, 1, 1, "" }, "1.1.1" },
+    { { "1.1.1-0-1-2", 1, 1, 1, "0-1-2" }, "1.1.1-0-1-2" },
+    { { "1.10.100-1000.0", 1, 10, 100, "1000.0" }, "1.10.100-1000.0" },
+    { { "1.2.3-SNAPSHOT", 1, 2, 3, "SNAPSHOT" }, "1.2.3-SNAPSHOT" },
+    { { "1.8.0-x-SNAPSHOT", 1, 8, 0, "x-SNAPSHOT" }, "1.8.0-x-SNAPSHOT" },
+    { { "0.1.2-a-b-c-d", 0, 1, 2, "a-b-c-d" }, "0.1.2-a-b-c-d" },
+    { { " 0 .1 .2 -x", 0, 1, 2, "x" }, "0.1.2-x" },
+    { { " 0 . 1 .  2 -x ", 0, 1, 2, "x" }, "0.1.2-x" },
+    { { "+1.+2.+3-6", 1, 2, 3, "6" }, "1.2.3-6" },
+    { { " +1 .  +2 .   +3 -100 .. ", 1, 2, 3, "100 .." }, "1.2.3-100 .." },
+    // no octals: leading zeros are just chopped off
+    { { "00.01.010", 0, 1, 10, "" }, "0.1.10" },
+    { { "  0.1.2----suffix  ", 0, 1, 2, "---suffix" }, "0.1.2----suffix" },
+    { { "0.1.2- - -x- -y- ", 0, 1, 2, " - -x- -y-" }, "0.1.2- - -x- -y-" },
   };
 
-  Version v;
   for (const auto& test_case : good_test_cases) {
-    ASSERT_OK(ParseVersion(test_case.raw_version, &v));
-    EXPECT_EQ(test_case, v);
+    const auto& version = test_case.first;
+    const auto& canonical_str = test_case.second;
+    Version v;
+    ASSERT_OK(ParseVersion(version.raw_version, &v));
+    EXPECT_EQ(version, v);
+    EXPECT_EQ(canonical_str, v.ToString());
   }
 
   const vector<string> bad_test_cases = {
     "",
+    " ",
+    "-",
+    " -",
+    "--",
+    " - - ",
+    "..",
+    "0.1",
+    "0.1.",
+    "0.1.+",
+    "+ 0.+ 1.+ 2",
+    "0.1.+-woops",
+    "0.1.2+",
+    "1..1",
+    ".0.1",
+    " . . ",
+    "-0.1.2-013",
+    "0.-1.2-013",
+    "0.1.-2-013",
+    "1000,000.2999,999.999",
+    "1e10.0.1",
+    "1e+10.0.1",
+    "1e-1.0.1",
+    "1E+1.2.3",
+    "1E-5.0.1",
     "foo",
     "foo.1.0",
+    "a.b.c",
+    "0.x1.x2",
+    "0x0.0x1.0x2",
     "1.bar.0",
     "1.0.foo",
     "1.0foo.bar",
     "foo5-1.4.3",
   };
 
-  for (const auto& test_case : bad_test_cases) {
-    ASSERT_TRUE(ParseVersion(test_case, &v).IsInvalidArgument());
+  for (const auto& input_str : bad_test_cases) {
+    Version v;
+    const auto s = ParseVersion(input_str, &v);
+    ASSERT_TRUE(s.IsInvalidArgument())
+        << s.ToString() << ": " << input_str << " ---> " << v.ToString();
   }
 }
 
+// Sanity check: parse current Kudu version string and make sure the 
'canonical'
+// representation of the parsed version matches the 'raw' input as is.
+TEST(VersionUtilTest, ParseCurrentKuduVersionString) {
+  const auto ver_string = VersionInfo::GetShortVersionInfo();
+  Version v;
+  const auto s = ParseVersion(ver_string, &v);
+  ASSERT_TRUE(s.ok()) << s.ToString();
+  EXPECT_EQ(ver_string, v.ToString());
+}
+
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/7e733a73/src/kudu/util/version_util.cc
----------------------------------------------------------------------
diff --git a/src/kudu/util/version_util.cc b/src/kudu/util/version_util.cc
index bd298f8..6715894 100644
--- a/src/kudu/util/version_util.cc
+++ b/src/kudu/util/version_util.cc
@@ -17,15 +17,17 @@
 
 #include "kudu/util/version_util.h"
 
-#include <iostream>
+#include <iterator>
 #include <string>
 #include <utility>
 #include <vector>
 
 #include <glog/logging.h>
 
+#include "kudu/gutil/strings/join.h"
 #include "kudu/gutil/strings/numbers.h"
 #include "kudu/gutil/strings/split.h"
+#include "kudu/gutil/strings/strip.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/util/status.h"
 
@@ -45,7 +47,9 @@ bool Version::operator==(const Version& other) const {
 }
 
 string Version::ToString() const {
-  return raw_version;
+  return extra.empty()
+      ? Substitute("$0.$1.$2", major, minor, maintenance)
+      : Substitute("$0.$1.$2-$3", major, minor, maintenance, extra);
 }
 
 ostream& operator<<(ostream& os, const Version& v) {
@@ -54,29 +58,32 @@ ostream& operator<<(ostream& os, const Version& v) {
 
 Status ParseVersion(const string& version_str,
                     Version* v) {
-  CHECK(v);
+  static const char* const kDelimiter = "-";
+
+  DCHECK(v);
   const Status invalid_ver_err =
       Status::InvalidArgument("invalid version string", version_str);
-  Version temp_v;
-  const vector<string> main_and_extra = Split(version_str, "-");
-  if (main_and_extra.empty() || main_and_extra.size() > 2) {
+  auto v_str = version_str;
+  StripWhiteSpace(&v_str);
+  const vector<string> main_and_extra = Split(v_str, kDelimiter);
+  if (main_and_extra.empty()) {
     return invalid_ver_err;
   }
-  if (main_and_extra.size() == 2) {
-    temp_v.extra = main_and_extra[1];
-  }
-  const auto& main_ver_str = main_and_extra[0];
-  const vector<string> maj_min_maint = Split(main_ver_str, ".");
+  const vector<string> maj_min_maint = Split(main_and_extra.front(), ".");
   if (maj_min_maint.size() != 3) {
     return invalid_ver_err;
   }
+  Version temp_v;
   if (!SimpleAtoi(maj_min_maint[0], &temp_v.major) ||
       !SimpleAtoi(maj_min_maint[1], &temp_v.minor) ||
       !SimpleAtoi(maj_min_maint[2], &temp_v.maintenance)) {
     return invalid_ver_err;
   }
+  temp_v.extra = JoinStringsIterator(std::next(main_and_extra.begin()),
+                                     main_and_extra.end(), kDelimiter);
   temp_v.raw_version = version_str;
   *v = std::move(temp_v);
+
   return Status::OK();
 }
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/7e733a73/src/kudu/util/version_util.h
----------------------------------------------------------------------
diff --git a/src/kudu/util/version_util.h b/src/kudu/util/version_util.h
index 5cde6cc..446934c 100644
--- a/src/kudu/util/version_util.h
+++ b/src/kudu/util/version_util.h
@@ -16,9 +16,10 @@
 // under the License.
 #pragma once
 
-#include <iostream>
+#include <iosfwd>
 #include <string>
 
+#include "kudu/gutil/port.h"
 #include "kudu/util/status.h"
 
 namespace kudu {
@@ -27,7 +28,7 @@ namespace kudu {
 //
 //  <major>.<minor>.<maintenance>[-<extra>]
 //
-// e.g. 1.6.0 or 1.7.1-SNAPSHOT.
+// e.g. 1.6.0, 1.7.1-SNAPSHOT, 1.8.0-RC1-SNAPSHOT, etc.
 //
 // This struct can be used with versions reported by ksck to determine if and
 // how certain tools should function depending on what versions are running in
@@ -35,6 +36,10 @@ namespace kudu {
 struct Version {
   bool operator==(const Version& other) const;
 
+  // Return 'canonical' version string, i.e. the concatenation of the version
+  // components transformed back into the string representation. The parser
+  // implementation has its quirks, so the canonical version string does not
+  // always match the raw input string.
   std::string ToString() const;
 
   // The original version string.
@@ -53,6 +58,6 @@ std::ostream& operator<<(std::ostream& os, const Version& v);
 
 // Parse 'version_str' into 'v'. 'v' must not be null.
 Status ParseVersion(const std::string& version_str,
-                    Version* v);
+                    Version* v) WARN_UNUSED_RESULT;
 
 } // namespace kudu

Reply via email to