power-utils: Add --compare option

This option is to get a latest version from a list of PSU versions.

Due to the --compare option requires a list of strings and only one
option is supported at the same time, it's easier to switch to CLI11 to
parse the arguments.

Also add --raw option that outputs the text without linefeed.

Tested: Verify both --get-version and --compare works on Witherspoon.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: Idec75e3a5699eba8ba587e74824431993fe10c4c
diff --git a/tools/power-utils/argument.cpp b/tools/power-utils/argument.cpp
deleted file mode 100644
index 570c97f..0000000
--- a/tools/power-utils/argument.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * Copyright © 2019 IBM Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "argument.hpp"
-
-#include <algorithm>
-#include <iostream>
-#include <iterator>
-
-namespace phosphor
-{
-namespace power
-{
-
-void ArgumentParser::usage(char** argv)
-{
-    std::cerr << "Usage: " << argv[0] << " [options] <psu-inventory-path>\n";
-    std::cerr << "Options:\n";
-    std::cerr << "    --help                Print this menu\n";
-    std::cerr << "    --get-version         Get PSU version\n";
-    std::cerr << std::flush;
-}
-
-const option ArgumentParser::options[] = {
-    {"get-version", required_argument, NULL, 'g'},
-    {"help", no_argument, NULL, 'h'},
-    {0, 0, 0, 0},
-};
-
-const char* ArgumentParser::optionStr = "g:h?";
-ArgumentParser::ArgumentParser(int argc, char** argv)
-{
-    int option = 0;
-    while (-1 != (option = getopt_long(argc, argv, optionStr, options, NULL)))
-    {
-        if ((option == '?') || (option == 'h'))
-        {
-            usage(argv);
-            exit(-1);
-        }
-
-        auto i = &options[0];
-        while ((i->val != option) && (i->val != 0))
-        {
-            ++i;
-        }
-
-        if (i->val)
-        {
-            arguments[i->name] = (i->has_arg ? optarg : trueString);
-        }
-    }
-}
-
-const std::string& ArgumentParser::operator[](const std::string& opt)
-{
-    auto i = arguments.find(opt);
-    if (i == arguments.end())
-    {
-        return emptyString;
-    }
-    else
-    {
-        return i->second;
-    }
-}
-
-const std::string ArgumentParser::trueString = "true";
-const std::string ArgumentParser::emptyString = "";
-
-} // namespace power
-} // namespace phosphor
diff --git a/tools/power-utils/main.cpp b/tools/power-utils/main.cpp
index e043821..19d0395 100644
--- a/tools/power-utils/main.cpp
+++ b/tools/power-utils/main.cpp
@@ -13,27 +13,45 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include "argument.hpp"
 #include "version.hpp"
 
+#include <CLI/CLI.hpp>
 #include <phosphor-logging/log.hpp>
 
-using namespace phosphor::power;
 using namespace phosphor::logging;
 
 int main(int argc, char** argv)
 {
-    ArgumentParser args{argc, argv};
-    auto psuPath = args["get-version"];
-    if (psuPath.empty())
+
+    std::string psuPath;
+    std::vector<std::string> versions;
+    bool rawOutput = false;
+
+    CLI::App app{"PSU utils app for OpenBMC"};
+    auto action = app.add_option_group("Action");
+    action->add_option("-g,--get-version", psuPath,
+                       "Get PSU version from inventory path");
+    action->add_option("-c,--compare", versions,
+                       "Compare and get the latest version");
+    action->require_option(1); // Only one option is supported
+    app.add_flag("--raw", rawOutput, "Output raw text without linefeed");
+    CLI11_PARSE(app, argc, argv);
+
+    std::string ret;
+
+    if (!psuPath.empty())
     {
-        log<level::ERR>("PSU Inventory path argument required");
-        args.usage(argv);
-        exit(1);
+        ret = version::getVersion(psuPath);
+    }
+    if (!versions.empty())
+    {
+        ret = version::getLatest(versions);
     }
 
-    // For now only get-version is supported
-    auto version = version::getVersion(psuPath);
-    printf("%s", version.c_str());
-    return version.empty() ? 1 : 0;
+    printf("%s", ret.c_str());
+    if (!rawOutput)
+    {
+        printf("\n");
+    }
+    return ret.empty() ? 1 : 0;
 }
diff --git a/tools/power-utils/meson.build b/tools/power-utils/meson.build
index fa193be..4f0d53f 100644
--- a/tools/power-utils/meson.build
+++ b/tools/power-utils/meson.build
@@ -1,6 +1,5 @@
 psutils = executable(
     'psutils',
-    'argument.cpp',
     'version.cpp',
     'main.cpp',
     dependencies: [
@@ -13,3 +12,5 @@
         libpower,
     ]
 )
+
+subdir('test')
diff --git a/tools/power-utils/test/meson.build b/tools/power-utils/test/meson.build
new file mode 100644
index 0000000..056f4cc
--- /dev/null
+++ b/tools/power-utils/test/meson.build
@@ -0,0 +1,18 @@
+test(
+    'test_version',
+    executable(
+        'test_version',
+        'test_version.cpp',
+        '../version.cpp',
+        dependencies: [
+            gtest,
+            phosphor_logging,
+        ],
+        implicit_include_directories: false,
+        include_directories: '../../..',
+        link_with: [
+            libpower,
+        ],
+        objects: record_manager,
+    )
+)
diff --git a/tools/power-utils/test/test_version.cpp b/tools/power-utils/test/test_version.cpp
new file mode 100644
index 0000000..20ab52b
--- /dev/null
+++ b/tools/power-utils/test/test_version.cpp
@@ -0,0 +1,45 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "../version.hpp"
+
+#include <gtest/gtest.h>
+
+TEST(Version, GetLatest)
+{
+    // Input 2 different version where primary versions are different
+    std::vector<std::string> input = {"00000110", "01100110"};
+    EXPECT_EQ("01100110", version::getLatest(input));
+
+    // Input 3 different version where secondary versions are different
+    input = {"11223366", "11223355", "11223344"};
+    EXPECT_EQ("11223366", version::getLatest(input));
+
+    // Input 3 different version where communication versions are different
+    input = {"111133336666", "111133338888", "111133332222"};
+    EXPECT_EQ("111133338888", version::getLatest(input));
+
+    // Input has 3 same versions
+    input = {"11112222", "11112222", "11112222"};
+    EXPECT_EQ("11112222", version::getLatest(input));
+
+    // Input has one version
+    input = {"11112222"};
+    EXPECT_EQ("11112222", version::getLatest(input));
+
+    // Input empty
+    input = {};
+    EXPECT_EQ("", version::getLatest(input));
+}
diff --git a/tools/power-utils/version.cpp b/tools/power-utils/version.cpp
index 1acbbf2..12bb7c9 100644
--- a/tools/power-utils/version.cpp
+++ b/tools/power-utils/version.cpp
@@ -74,6 +74,21 @@
     }
     return std::make_tuple(*devicePath, type, versionStr);
 }
+
+// A default implemention compare the string itself
+std::string getLatestDefault(const std::vector<std::string>& versions)
+{
+    std::string latest;
+    for (const auto& version : versions)
+    {
+        if (latest < version)
+        {
+            latest = version;
+        }
+    }
+    return latest;
+}
+
 } // namespace utils
 
 namespace version
@@ -100,4 +115,25 @@
     return version;
 }
 
+std::string getLatest(const std::vector<std::string>& versions)
+{
+    // TODO: when multiple PSU/Machines are supported, add configuration options
+    // to implement machine-specific logic.
+    // For now IBM AC Servers and Inspur FP5280G2 are supported.
+    //
+    // IBM AC servers' PSU version has two types:
+    // * XXXXYYYYZZZZ: XXXX is the primary version
+    //                 YYYY is the secondary version
+    //                 ZZZZ is the communication version
+    //
+    // * XXXXYYYY:     XXXX is the primary version
+    //                 YYYY is the seconday version
+    //
+    // Inspur FP5280G2 PSU version is human readable text and a larger string
+    // means a newer version.
+    //
+    // So just compare by strings is OK for these cases
+    return utils::getLatestDefault(versions);
+}
+
 } // namespace version
diff --git a/tools/power-utils/version.hpp b/tools/power-utils/version.hpp
index 26f0ce6..c10071a 100644
--- a/tools/power-utils/version.hpp
+++ b/tools/power-utils/version.hpp
@@ -16,6 +16,7 @@
 #pragma once
 
 #include <string>
+#include <vector>
 
 namespace version
 {
@@ -24,7 +25,18 @@
  * Get the software version of the PSU
  *
  * @param[in] psuInventoryPath - The inventory path of the PSU
+ *
+ * @return The version of the PSU
  */
 std::string getVersion(const std::string& psuInventoryPath);
 
+/**
+ * Get the latest version from a list of versions
+ *
+ * @param[in] versions - The list of PSU version strings
+ *
+ * @return The latest version
+ */
+std::string getLatest(const std::vector<std::string>& versions);
+
 } // namespace version