argument: Implement test cases

The argument parser had a few subtle bugs and could use some tests.

Change-Id: I0adbc507a822a6887fbf02b888b1f678b3cc7b35
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/test/Makefile.am b/test/Makefile.am
index 173e0e2..acb03b0 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -4,7 +4,8 @@
 TESTS = $(check_PROGRAMS)
 
 # Build/add utest to test suite
-check_PROGRAMS = timer_test \
+check_PROGRAMS = argument_test \
+                 timer_test \
                  watchdog_test
 
 utestCPPFLAGS = $(GTEST_MAIN_CFLAGS) \
@@ -22,6 +23,10 @@
                $(PHOSPHOR_LOGGING_LIBS) \
                $(PHOSPHOR_DBUS_INTERFACES_LIBS)
 
+argument_test_CPPFLAGS = ${utestCPPFLAGS}
+argument_test_CXXFLAGS = ${utestCXXFLAGS}
+argument_test_LDFLAGS = ${utestLDFLAGS}
+
 timer_test_CPPFLAGS = ${utestCPPFLAGS}
 timer_test_CXXFLAGS = ${utestCXXFLAGS}
 timer_test_LDFLAGS = ${utestLDFLAGS}
@@ -30,9 +35,12 @@
 watchdog_test_CXXFLAGS = ${utestCXXFLAGS}
 watchdog_test_LDFLAGS = ${utestLDFLAGS}
 
+argument_test_SOURCES = argument_test.cpp
 timer_test_SOURCES = timer_test.cpp
 watchdog_test_SOURCES = watchdog_test.cpp
 
+argument_test_LDADD = $(top_builddir)/argument.o
+
 timer_test_LDADD =  $(top_builddir)/timer.o
 
 watchdog_test_LDADD = $(top_builddir)/watchdog.o \
diff --git a/test/argument_test.cpp b/test/argument_test.cpp
new file mode 100644
index 0000000..79b936f
--- /dev/null
+++ b/test/argument_test.cpp
@@ -0,0 +1,166 @@
+#include <string>
+
+#include "argument.hpp"
+#include "argument_test.hpp"
+
+static const std::string expected_path1 = "/arg1-test-path";
+static const std::string expected_target1 = "t1.target";
+
+// Allow for a single unrecognized option then the Usage printout
+static const std::string invalid_arg_regex =
+        "^[^\n]*unrecognized option[^\n]*\nUsage: ";
+
+static const std::string clean_usage_regex = "^Usage: ";
+
+namespace phosphor
+{
+namespace watchdog
+{
+
+void ArgumentTest::SetUp()
+{
+    arg0 = "argument_test";
+}
+
+/** @brief ArgumentParser should return no values if given no options */
+TEST_F(ArgumentTest, NoOptions)
+{
+    char * const args[] = {
+        &arg0[0], nullptr
+    };
+    ArgumentParser ap(sizeof(args)/sizeof(char *) - 1, args);
+    EXPECT_EQ(ArgumentParser::emptyString, ap["path"]);
+    EXPECT_EQ(ArgumentParser::emptyString, ap["continue"]);
+    EXPECT_EQ(ArgumentParser::emptyString, ap["arbitrary_unknown"]);
+}
+
+/** @brief ArgumentParser should return true for an existing no-arg option
+ *         Make sure we don't parse arguments if an option takes none
+ *         Also make sure we don't populate options not used.
+ */
+TEST_F(ArgumentTest, LongOptionNoArg)
+{
+    std::string arg_continue = "--continue";
+    std::string arg_extra = "not-a-bool";
+    char * const args[] = {
+        &arg0[0], &arg_continue[0], &arg_extra[0], nullptr
+    };
+    ArgumentParser ap(sizeof(args)/sizeof(char *) - 1, args);
+    EXPECT_EQ(ArgumentParser::emptyString, ap["path"]);
+    EXPECT_EQ(ArgumentParser::trueString, ap["continue"]);
+}
+
+/** @brief ArgumentParser should return a string for long options that
+ *         take an arg
+ */
+TEST_F(ArgumentTest, LongOptionRequiredArg)
+{
+    std::string arg_path = "--path";
+    std::string arg_path_val = expected_path1;
+    std::string arg_extra = "/unused-path";
+    char * const args[] = {
+        &arg0[0], &arg_path[0], &arg_path_val[0], &arg_extra[0], nullptr
+    };
+    ArgumentParser ap(sizeof(args)/sizeof(char *) - 1, args);
+    EXPECT_EQ(expected_path1, ap["path"]);
+}
+
+/** @brief ArgumentParser should return a string for long options that
+ *         accept an argument when passed an argument inline
+ */
+TEST_F(ArgumentTest, LongOptionInlineArg)
+{
+    std::string arg_path = "--path=" + expected_path1;
+    std::string arg_extra = "/unused-path";
+    char * const args[] = {
+        &arg0[0], &arg_path[0], &arg_extra[0], nullptr
+    };
+    ArgumentParser ap(sizeof(args)/sizeof(char *) - 1, args);
+    EXPECT_EQ(expected_path1, ap["path"]);
+}
+
+/** @brief ArgumentParser should return a string for short options that
+ *         accept an argument.
+ */
+TEST_F(ArgumentTest, ShortOptionRequiredArg)
+{
+    std::string arg_path = "-p";
+    std::string arg_path_val = expected_path1;
+    std::string arg_extra = "/unused-path";
+    char * const args[] = {
+        &arg0[0], &arg_path[0], &arg_path_val[0], &arg_extra[0], nullptr
+    };
+    ArgumentParser ap(sizeof(args)/sizeof(char *) - 1, args);
+    EXPECT_EQ(expected_path1, ap["path"]);
+}
+
+/** @brief ArgumentParser should be able to handle parsing multiple options
+ *         Make sure that when passed multiple of the same option it uses
+ *         the argument to the option passed last
+ *         Make sure this works for no-arg and required-arg type options
+ *         Make sure this works between short and long options
+ */
+TEST_F(ArgumentTest, MultiOptionOverride)
+{
+    std::string arg_continue_short = "-c";
+    std::string arg_path = "--path=" + expected_path1;
+    std::string arg_continue_long = "--continue";
+    std::string arg_target = "--target=/unused-path";
+    std::string arg_target_short = "-t";
+    std::string arg_target_val = expected_target1;
+    char * const args[] = {
+        &arg0[0], &arg_continue_short[0], &arg_path[0], &arg_continue_long[0],
+        &arg_target[0], &arg_target_short[0], &arg_target_val[0], nullptr
+    };
+    ArgumentParser ap(sizeof(args)/sizeof(char *) - 1, args);
+    EXPECT_EQ(expected_path1, ap["path"]);
+    EXPECT_EQ(ArgumentParser::trueString, ap["continue"]);
+    EXPECT_EQ(expected_target1, ap["target"]);
+}
+
+/** @brief ArgumentParser should print usage information when given a help
+ *         argument anywhere in the arguments array
+ */
+TEST_F(ArgumentTest, ShortOptionHelp)
+{
+    std::string arg_extra = "extra";
+    std::string arg_help = "-h";
+    char * const args[] = {
+        &arg0[0], &arg_extra[0], &arg_help[0], nullptr
+    };
+    EXPECT_EXIT(ArgumentParser(sizeof(args)/sizeof(char *) - 1, args),
+            ::testing::ExitedWithCode(255), clean_usage_regex);
+}
+
+/** @brief ArgumentParser should print usage information when given a help
+ *         argument anywhere in the arguments array
+ */
+TEST_F(ArgumentTest, LongOptionHelp)
+{
+    std::string arg_help = "--help";
+    std::string arg_extra = "extra";
+    char * const args[] = {
+        &arg0[0], &arg_help[0], &arg_extra[0], nullptr
+    };
+    EXPECT_EXIT(ArgumentParser(sizeof(args)/sizeof(char *) - 1, args),
+            ::testing::ExitedWithCode(255), clean_usage_regex);
+}
+
+/** @brief ArgumentParser should print an invalid argument error and
+ *         usage information when given an invalid argument anywhere
+ *         in the arguments array
+ */
+TEST_F(ArgumentTest, InvalidOptionHelp)
+{
+    std::string arg_continue = "--continue";
+    std::string arg_bad = "--bad_arg";
+    std::string arg_target = "--target=/unused-path";
+    char * const args[] = {
+        &arg0[0], &arg_continue[0], &arg_bad[0], &arg_target[0], nullptr
+    };
+    EXPECT_EXIT(ArgumentParser(sizeof(args)/sizeof(char *) - 1, args),
+            ::testing::ExitedWithCode(255), invalid_arg_regex);
+}
+
+}  // namespace watchdog
+}  // namespace phosphor
diff --git a/test/argument_test.hpp b/test/argument_test.hpp
new file mode 100644
index 0000000..9e8c565
--- /dev/null
+++ b/test/argument_test.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace phosphor
+{
+namespace watchdog
+{
+
+class ArgumentTest : public testing::Test
+{
+    protected:
+        void SetUp() override;
+
+        std::string arg0;
+};
+
+}  // namespace watchdog
+}  // namespace phosphor