base-stats,tests: Add unit test for Stats::Info

Add a unit test for stats/info.

One test has been disabled due to not knowing the
expected behavior.

It is important to notice that Stats::Info can have
duplicate names using the new style. Stats::Group is
responsible for not allowing duplicate names in this
case.

Change-Id: I8b169d34c1309b37ba79fa9cf6895547b7e97fc0
Signed-off-by: Daniel R. Carvalho <odanrc@yahoo.com.br>
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/43009
Reviewed-by: Bobby R. Bruce <bbruce@ucdavis.edu>
Maintainer: Bobby R. Bruce <bbruce@ucdavis.edu>
Tested-by: kokoro <noreply+kokoro@google.com>
diff --git a/src/base/stats/SConscript b/src/base/stats/SConscript
index 1e275fa..f44ef03 100644
--- a/src/base/stats/SConscript
+++ b/src/base/stats/SConscript
@@ -40,6 +40,7 @@
     else:
         Source('hdf5.cc')
 
+GTest('info.test', 'info.test.cc', 'info.cc', '../debug.cc', '../str.cc')
 GTest('storage.test', 'storage.test.cc', '../debug.cc', '../str.cc', 'info.cc',
     'storage.cc', '../../sim/cur_tick.cc')
 GTest('units.test', 'units.test.cc')
diff --git a/src/base/stats/info.cc b/src/base/stats/info.cc
index c7dd5c9..edc4290 100644
--- a/src/base/stats/info.cc
+++ b/src/base/stats/info.cc
@@ -42,8 +42,6 @@
 #include "base/stats/info.hh"
 
 #include <cctype>
-#include <map>
-#include <string>
 
 #include "base/cprintf.hh"
 #include "base/debug.hh"
diff --git a/src/base/stats/info.hh b/src/base/stats/info.hh
index f8dfce7..36fb42c 100644
--- a/src/base/stats/info.hh
+++ b/src/base/stats/info.hh
@@ -29,6 +29,11 @@
 #ifndef __BASE_STATS_INFO_HH__
 #define __BASE_STATS_INFO_HH__
 
+#include <cstdint>
+#include <map>
+#include <string>
+#include <vector>
+
 #include "base/compiler.hh"
 #include "base/flags.hh"
 #include "base/stats/types.hh"
diff --git a/src/base/stats/info.test.cc b/src/base/stats/info.test.cc
new file mode 100644
index 0000000..b56460c
--- /dev/null
+++ b/src/base/stats/info.test.cc
@@ -0,0 +1,322 @@
+/*
+ * Copyright (c) 2021 Daniel R. Carvalho
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <gtest/gtest-spi.h>
+#include <gtest/gtest.h>
+
+#include "base/stats/info.hh"
+
+using namespace gem5;
+
+class TestInfo : public statistics::Info
+{
+  public:
+    using statistics::Info::Info;
+
+    int value = 0;
+
+    bool check() const override { return true; }
+    void prepare() override {}
+    void reset() override { value = 0; }
+    bool zero() const override { return false; }
+    void visit(statistics::Output &visitor) override {}
+};
+
+/** Test that a name is properly assigned under the new style. */
+TEST(StatsInfoTest, NameNewStyle)
+{
+    TestInfo info;
+    info.setName("InfoNameNewStyle", false);
+    ASSERT_EQ(info.name, "InfoNameNewStyle");
+}
+
+/** Test that the first character accepts alpha and underscore. */
+TEST(StatsInfoTest, NameFirstCharacter)
+{
+    TestInfo info;
+    info.setName("X", false);
+    ASSERT_EQ(info.name, "X");
+
+    TestInfo info2;
+    info2.setName("a", false);
+    ASSERT_EQ(info2.name, "a");
+
+    TestInfo info3;
+    info3.setName("_", false);
+    ASSERT_EQ(info3.name, "_");
+
+    TestInfo info4;
+    info4.setName("X.Y", false);
+    ASSERT_EQ(info4.name, "X.Y");
+
+    TestInfo info5;
+    info5.setName("a.b._.d.e", false);
+    ASSERT_EQ(info5.name, "a.b._.d.e");
+}
+
+/** Test that the non-first characters accept alpha-numeric and underscore. */
+TEST(StatsInfoTest, NameOtherCharacters)
+{
+    TestInfo info;
+    info.setName("abCde", false);
+    ASSERT_EQ(info.name, "abCde");
+
+    TestInfo info2;
+    info2.setName("ab_de", false);
+    ASSERT_EQ(info2.name, "ab_de");
+
+    TestInfo info3;
+    info3.setName("ab9de", false);
+    ASSERT_EQ(info3.name, "ab9de");
+
+    TestInfo info4;
+    info4.setName("a_bCD12_ef", false);
+    ASSERT_EQ(info4.name, "a_bCD12_ef");
+
+    TestInfo info5;
+    info5.setName("a_._bC.D12_ef", false);
+    ASSERT_EQ(info5.name, "a_._bC.D12_ef");
+}
+
+/**
+ * Test that a name is properly assigned under the old style. The name map
+ * must be checked to make sure it can resolved.
+ */
+TEST(StatsInfoTest, NameOldStyle)
+{
+    TestInfo info;
+    info.setName("InfoNameOldStyle", true);
+    ASSERT_EQ(info.name, "InfoNameOldStyle");
+
+    const auto &it = statistics::nameMap().find(info.name);
+    ASSERT_NE(it, statistics::nameMap().cend());
+    ASSERT_EQ(info.id, it->second->id);
+}
+
+/**
+ * @todo Test the behavior when set name is called twice on the same old-style
+ * info. Should it be allowed? If so, the old name should be removed from the
+ * name map.
+ */
+TEST(StatsInfoTest, DISABLED_NameOldStyleTwice)
+{
+    TestInfo info;
+    std::string old_name = "InfoNameOldStyleTwice";
+    info.setName(old_name, true);
+    info.setName("InfoNameOldStyleTwice2", true);
+    ASSERT_EQ(info.name, "InfoNameOldStyleTwice2");
+
+    const auto &it = statistics::nameMap().find(old_name);
+    ASSERT_EQ(it, statistics::nameMap().cend());
+}
+
+/**
+ * Test that a name can be duplicated when using the new styles. Duplicating
+ * is not a problem that must be solved by Info in this case. The stats name
+ * resolver does that.
+ */
+TEST(StatsInfoTest, NameNewStyleDuplicate)
+{
+    TestInfo info;
+    std::string name = "InfoNameNewStyleDuplicate";
+    info.setName(name, false);
+    EXPECT_EQ(info.name, name);
+
+    TestInfo info2;
+    info2.setName(name, false);
+    ASSERT_EQ(info2.name, name);
+}
+
+/**
+ * Test that a name can be duplicated when mixing styles. Duplicating
+ * is not a problem that must be solved by Info in this case. The stats
+ * name resolver does that.
+ */
+TEST(StatsInfoTest, NameMixStyleDuplicate)
+{
+    TestInfo info;
+    std::string name = "InfoNameMixStyleDuplicate";
+    info.setName(name, false);
+    EXPECT_EQ(info.name, name);
+
+    TestInfo info2;
+    info2.setName(name, true);
+    ASSERT_EQ(info2.name, name);
+}
+
+/** Test changing the separator. */
+TEST(StatsInfoTest, Separator)
+{
+    TestInfo info;
+
+    // Get current separator
+    auto separator = info.separatorString;
+
+    // Do the test with EXPECT_ so that we can restore the separator later
+    info.setSeparator(",-=-,");
+    EXPECT_EQ(statistics::Info::separatorString, ",-=-,");
+
+    // Restore separator
+    info.setSeparator(separator);
+}
+
+/** Test changing the less operation, which is applied on the name. */
+TEST(StatsInfoTest, Less)
+{
+    TestInfo info;
+    info.setName("Less1");
+    TestInfo info2;
+    info2.setName("Less2");
+
+    ASSERT_TRUE(statistics::Info::less(&info, &info2));
+    ASSERT_FALSE(statistics::Info::less(&info2, &info));
+}
+
+/** Test that checking Info after setting the init flag succeeds. */
+TEST(StatsInfoTest, BaseCheckInit)
+{
+    TestInfo info;
+    info.flags.set(statistics::init);
+    ASSERT_TRUE(info.baseCheck());
+}
+
+/** Test that checking Info for display after setting the name succeeds. */
+TEST(StatsInfoTest, BaseCheckDisplay)
+{
+    TestInfo info;
+    info.setName("BaseCheckDisplay");
+    info.flags.set(statistics::init | statistics::display);
+    ASSERT_TRUE(info.baseCheck());
+}
+
+/**
+ * Test changing the less operation, which is applied on the name (sub-groups).
+ */
+TEST(StatsInfoTest, LessSub)
+{
+    TestInfo info;
+    info.setName("Less.Sub2.a");
+    TestInfo info2;
+    info2.setName("Less.Sub1.b");
+
+    ASSERT_TRUE(statistics::Info::less(&info2, &info));
+    ASSERT_FALSE(statistics::Info::less(&info, &info2));
+}
+
+/** Test that a name cannot be empty. */
+TEST(StatsInfoDeathTest, NameEmpty)
+{
+    TestInfo info;
+    ASSERT_ANY_THROW(info.setName("", false));
+}
+
+/** Test that a sub-group's name cannot be empty. */
+TEST(StatsInfoDeathTest, NameSubEmpty)
+{
+    TestInfo info;
+    ASSERT_ANY_THROW(info.setName(".a", false));
+}
+
+/** Test that a sub-group's name cannot be empty. */
+TEST(StatsInfoDeathTest, NameSubEmpty2)
+{
+    TestInfo info;
+    ASSERT_ANY_THROW(info.setName("A.", false));
+}
+
+/** Test that a sub-group's name cannot be empty. */
+TEST(StatsInfoDeathTest, NameSubEmpty3)
+{
+    TestInfo info;
+    ASSERT_ANY_THROW(info.setName("a.b..c", false));
+}
+
+/** Test that the first character does not accept numbers. */
+TEST(StatsInfoDeathTest, NameFirstCharacterNumber)
+{
+    TestInfo info;
+    ASSERT_ANY_THROW(info.setName("1", false));
+}
+
+/** Test that the first character does not accept numbers (sub-group). */
+TEST(StatsInfoDeathTest, NameFirstCharacterNumberSub)
+{
+    TestInfo info;
+    ASSERT_ANY_THROW(info.setName("A.1", false));
+}
+
+/** Test that the first character does not accept special characters. */
+TEST(StatsInfoDeathTest, NameFirstCharacterSpecial)
+{
+    TestInfo info;
+    ASSERT_ANY_THROW(info.setName("!", false));
+}
+
+/**
+ * Test that the first character does not accept special characters
+ * (sub-group).
+ */
+TEST(StatsInfoDeathTest, NameFirstCharacterSpecialSub)
+{
+    TestInfo info;
+    ASSERT_ANY_THROW(info.setName("A.!", false));
+}
+
+/** Test that the non-first characters do not accept special characters. */
+TEST(StatsInfoDeathTest, NameOtherCharacterSpecial)
+{
+    TestInfo info;
+    ASSERT_ANY_THROW(info.setName("ab!de", false));
+}
+
+/** Test that a name cannot be duplicated under the old style. */
+TEST(StatsInfoDeathTest, NameOldStyleDuplicate)
+{
+    TestInfo info;
+    std::string name = "InfoNameOldStyleDuplicate";
+    info.setName(name, true);
+    EXPECT_EQ(info.name, name);
+
+    TestInfo info2;
+    ASSERT_ANY_THROW(info2.setName(name, true));
+}
+
+/** Test that checking Info without setting the init flag fails. */
+TEST(StatsInfoDeathTest, BaseCheckNoInit)
+{
+    TestInfo info;
+    ASSERT_ANY_THROW(info.baseCheck());
+}
+
+/** Test that checking Info for display without setting the name fails. */
+TEST(StatsInfoDeathTest, BaseCheckDisplayNoName)
+{
+    TestInfo info;
+    info.flags.set(statistics::init | statistics::display);
+    ASSERT_ANY_THROW(info.baseCheck());
+}