Add testcases for property watches

Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
Change-Id: I9e9266901414c71a34d9686f2a45bc4764602d53
diff --git a/src/test/.gitignore b/src/test/.gitignore
index e1da423..c3c7a0c 100644
--- a/src/test/.gitignore
+++ b/src/test/.gitignore
@@ -4,3 +4,6 @@
 /pathgentest.hpp
 /propertygentest
 /propertygentest.hpp
+/propertywatchtest
+/propertywatchgentest.hpp
+/propertywatchgentest
diff --git a/src/test/Makefile.am b/src/test/Makefile.am
index 330bf3e..fe66203 100644
--- a/src/test/Makefile.am
+++ b/src/test/Makefile.am
@@ -7,6 +7,7 @@
 TEMPLATESEARCH+=${srcdir}/templates
 
 check_PROGRAMS =
+noinst_PROGRAMS =
 BUILT_SOURCES =
 CLEANFILES =
 
@@ -61,3 +62,45 @@
 		-d yaml/propertygentest \
 		-o $(builddir)/$@ \
 		generate-cpp
+
+check_PROGRAMS += propertywatchgentest
+propertywatchgentest_SOURCES = \
+	propertywatchgentest.cpp
+propertywatchgentest_CXXFLAGS = \
+	$(gtest_cflags)
+propertywatchgentest_LDFLAGS = \
+	$(OESDK_TESTCASE_FLAGS)
+propertywatchgentest_LDADD = \
+	${gtest_ldadd}
+
+BUILT_SOURCES += propertywatchgentest.hpp
+CLEANFILES += propertywatchgentest.hpp
+
+PROPERTY_WATCH_TEST_GEN_DEPS = \
+	templates/propertywatchgentest.mako.hpp \
+	yaml/propertywatchgentest
+
+propertywatchgentest.hpp: $(PROPERTY_WATCH_TEST_GEN_DEPS)
+	$(AM_V_GEN) $(PYTHON) $(PDMGEN) \
+		-t propertywatchgentest.mako.hpp \
+		-p "${TEMPLATESEARCH}" \
+		-d yaml/propertywatchgentest \
+		-o $(builddir)/$@ \
+		generate-cpp
+
+# propertywatchtest is intentionally omitted from
+# check_PROGRAMS until a bug that manifests with
+# with GCC7 can be resolved.
+
+noinst_PROGRAMS += propertywatchtest
+propertywatchtest_SOURCES = \
+	propertywatchtest.cpp
+propertywatchtest_CXXFLAGS = \
+	$(gtest_cflags) \
+	${SDBUSPLUS_CFLAGS}
+propertywatchtest_LDFLAGS = \
+	$(OESDK_TESTCASE_FLAGS)
+propertywatchtest_LDADD = \
+	${gtest_ldadd} \
+	${SDBUSPLUS_LIBS} \
+	$(builddir)/../propertywatch.o
diff --git a/src/test/propertywatchgentest.cpp b/src/test/propertywatchgentest.cpp
new file mode 100644
index 0000000..afd9dbc
--- /dev/null
+++ b/src/test/propertywatchgentest.cpp
@@ -0,0 +1,91 @@
+#include <array>
+#include <string>
+#include <gtest/gtest.h>
+#include "data_types.hpp"
+
+using namespace std::string_literals;
+using namespace phosphor::dbus::monitoring;
+
+using Index = std::map<std::tuple<size_t, size_t, size_t>, size_t>;
+
+#include "propertywatchgentest.hpp"
+
+auto expectedStorageCount = 16;
+
+const std::array<Index, 4> expectedIndicies =
+{
+    {
+        {
+            {Index::key_type{0, 0, 0}, 0},
+            {Index::key_type{0, 1, 0}, 1},
+            {Index::key_type{1, 0, 0}, 2},
+            {Index::key_type{1, 1, 0}, 3},
+            {Index::key_type{2, 0, 0}, 4},
+            {Index::key_type{2, 1, 0}, 5},
+            {Index::key_type{3, 0, 0}, 6},
+            {Index::key_type{3, 1, 0}, 7},
+        },
+        {
+            {Index::key_type{2, 2, 1}, 8},
+            {Index::key_type{2, 2, 2}, 9},
+            {Index::key_type{3, 2, 1}, 10},
+            {Index::key_type{3, 2, 2}, 11},
+            {Index::key_type{4, 2, 1}, 12},
+            {Index::key_type{4, 2, 2}, 13},
+            {Index::key_type{5, 2, 1}, 14},
+            {Index::key_type{5, 2, 2}, 15},
+        },
+        {
+            {Index::key_type{3, 0, 0}, 6},
+        },
+        {
+            {Index::key_type{3, 2, 2}, 11},
+            {Index::key_type{5, 2, 2}, 15},
+        },
+    }
+};
+
+const std::array<std::tuple<std::string, size_t>, 4> expectedWatches =
+{
+    {
+        std::tuple<std::string, size_t>{"std::string"s, 0},
+        std::tuple<std::string, size_t>{"uint32_t"s, 1},
+        std::tuple<std::string, size_t>{"int32_t"s, 2},
+        std::tuple<std::string, size_t>{"std::string"s, 3},
+    }
+};
+
+TEST(PropertyWatchGenTest, storageCount)
+{
+    ASSERT_EQ(expectedStorageCount, storageCount);
+}
+
+TEST(PropertyWatchGenTest, IndiciesSameSize)
+{
+    ASSERT_EQ(sizeof(expectedIndicies), sizeof(indicies));
+}
+
+TEST(PropertyWatchGenTest, WatchesSameSize)
+{
+    ASSERT_EQ(sizeof(expectedWatches), sizeof(watches));
+}
+
+TEST(PropertyWatchGenTest, WatchesSameContent)
+{
+    size_t i;
+    for (i = 0; i < expectedWatches.size(); ++i)
+    {
+        ASSERT_EQ(watches[i],
+                  expectedWatches[i]);
+    }
+}
+
+TEST(PropertyWatchGenTest, IndiciesSameContent)
+{
+    size_t i;
+    for (i = 0; i < expectedIndicies.size(); ++i)
+    {
+        ASSERT_EQ(indicies[i],
+                  expectedIndicies[i]);
+    }
+}
diff --git a/src/test/propertywatchtest.cpp b/src/test/propertywatchtest.cpp
new file mode 100644
index 0000000..b92dbec
--- /dev/null
+++ b/src/test/propertywatchtest.cpp
@@ -0,0 +1,225 @@
+#include <array>
+#include "propertywatchimpl.hpp"
+#include "propertywatchtest.hpp"
+
+using namespace std::string_literals;
+using namespace phosphor::dbus::monitoring;
+
+const std::array<std::string, 4> paths =
+{
+    "/xyz/openbmc_project/testing/inst1"s,
+    "/xyz/openbmc_project/testing/inst2"s,
+    "/xyz/openbmc_project/testing/inst3"s,
+    "/xyz/openbmc_project/testing/inst4"s,
+};
+
+const std::array<std::string, 2> interfaces =
+{
+    "xyz.openbmc_project.Iface1"s,
+    "xyz.openbmc_project.Iface2"s,
+};
+
+const std::array<std::string, 2> properties =
+{
+    "Value1"s,
+    "Value2"s,
+};
+
+const std::string meta;
+
+std::array<any_ns::any, 8> storage = { };
+
+const PropertyIndex watchIndex =
+{
+    {
+        {
+            PropertyIndex::key_type{paths[0], interfaces[0], properties[0]},
+            PropertyIndex::mapped_type{meta, meta, storage[0]}
+        },
+        {
+            PropertyIndex::key_type{paths[0], interfaces[1], properties[1]},
+            PropertyIndex::mapped_type{meta, meta, storage[1]}
+        },
+        {
+            PropertyIndex::key_type{paths[1], interfaces[0], properties[0]},
+            PropertyIndex::mapped_type{meta, meta, storage[2]}
+        },
+        {
+            PropertyIndex::key_type{paths[1], interfaces[1], properties[1]},
+            PropertyIndex::mapped_type{meta, meta, storage[3]}
+        },
+        {
+            PropertyIndex::key_type{paths[2], interfaces[0], properties[0]},
+            PropertyIndex::mapped_type{meta, meta, storage[4]}
+        },
+        {
+            PropertyIndex::key_type{paths[2], interfaces[1], properties[1]},
+            PropertyIndex::mapped_type{meta, meta, storage[5]}
+        },
+        {
+            PropertyIndex::key_type{paths[3], interfaces[0], properties[0]},
+            PropertyIndex::mapped_type{meta, meta, storage[6]}
+        },
+        {
+            PropertyIndex::key_type{paths[3], interfaces[1], properties[1]},
+            PropertyIndex::mapped_type{meta, meta, storage[7]}
+        },
+    },
+};
+
+template <typename T> struct ExpectedValues {};
+template <> struct ExpectedValues<uint8_t>
+{
+    static auto& get(size_t i)
+    {
+        static const std::array<uint8_t, 8> values =
+        {
+            {0, 1, 2, 3, 4, 5, 6, 7},
+        };
+        return values[i];
+    }
+};
+
+template <> struct ExpectedValues<uint16_t>
+{
+    static auto& get(size_t i)
+    {
+        static const std::array<uint16_t, 8> values =
+        {
+            {88, 77, 66, 55, 44, 33, 22, 11},
+        };
+        return values[i];
+    }
+};
+
+template <> struct ExpectedValues<uint32_t>
+{
+    static auto& get(size_t i)
+    {
+        static const std::array<uint32_t, 8> values =
+        {
+            {0xffffffff, 1, 3, 0, 5, 7, 9, 0xffffffff},
+        };
+        return values[i];
+    }
+};
+
+template <> struct ExpectedValues<uint64_t>
+{
+    static auto& get(size_t i)
+    {
+        static const std::array<uint64_t, 8> values =
+        {
+            {0xffffffffffffffff, 3, 7, 12234, 0, 3, 9, 0xffffffff},
+        };
+        return values[i];
+    }
+};
+
+template <> struct ExpectedValues<std::string>
+{
+    static auto& get(size_t i)
+    {
+        static const std::array<std::string, 8> values =
+        {
+            {""s, "foo"s, "bar"s, "baz"s, "hello"s, "string", "\x2\x3", "\\"},
+        };
+        return values[i];
+    }
+};
+
+template <typename T>
+void testStart()
+{
+    using ::testing::Return;
+    using ::testing::_;
+
+    MockDBusInterface dbus;
+    MockDBusInterface::instance(dbus);
+
+    const std::vector<std::string> expectedMapperInterfaces;
+    PropertyWatchOfType<T, MockDBusInterface> watch(watchIndex);
+
+    auto ndx = static_cast<size_t>(0);
+    for (const auto& o : convert(watchIndex))
+    {
+        const auto& path = o.first.get();
+        const auto& interfaces = o.second;
+        std::vector<std::string> mapperResponse;
+        std::transform(
+            interfaces.begin(),
+            interfaces.end(),
+            std::back_inserter(mapperResponse),
+        // *INDENT-OFF*
+            [](const auto & item)
+            {
+                return item.first;
+            });
+        // *INDENT-ON*
+        EXPECT_CALL(
+            dbus,
+            mapperGetObject(
+                MAPPER_BUSNAME,
+                MAPPER_PATH,
+                MAPPER_INTERFACE,
+                "GetObject",
+                path,
+                expectedMapperInterfaces))
+        .WillOnce(Return(GetObject({{"", mapperResponse}})));
+        EXPECT_CALL(
+            dbus,
+            fwdAddMatch(
+                sdbusplus::bus::match::rules::member("InterfacesAdded") +
+                sdbusplus::bus::match::rules::path(path) +
+                sdbusplus::bus::match::rules::interface(
+                    "org.freedesktop.DBus.ObjectManager"),
+                _));
+        for (const auto& i : interfaces)
+        {
+            const auto& interface = i.first.get();
+            const auto& properties = i.second;
+            EXPECT_CALL(
+                dbus,
+                fwdAddMatch(
+                    sdbusplus::bus::match::rules::member("PropertiesChanged") +
+                    sdbusplus::bus::match::rules::path(path) +
+                    sdbusplus::bus::match::rules::argN(0, interface) +
+                    sdbusplus::bus::match::rules::interface(
+                        "org.freedesktop.DBus.Properties"),
+                    _));
+
+            PropertiesChanged<T> serviceResponse;
+            for (const auto& p : properties)
+            {
+                serviceResponse[p] = ExpectedValues<T>::get(ndx);
+                ++ndx;
+            }
+            Expect<T>::getProperties(dbus, path, interface)
+            .WillOnce(Return(serviceResponse));
+        }
+    }
+
+    watch.start();
+
+    ndx = 0;
+    for (auto s : storage)
+    {
+        ASSERT_EQ(s.empty(), false);
+        ASSERT_EQ(any_ns::any_cast<T>(s), ExpectedValues<T>::get(ndx));
+        ++ndx;
+    }
+
+    // Make sure start logic only runs the first time.
+    watch.start();
+}
+
+TEST(PropertyWatchTest, TestStart)
+{
+    testStart<uint8_t>();
+    testStart<uint16_t>();
+    testStart<uint32_t>();
+    testStart<uint64_t>();
+    testStart<std::string>();
+}
+
+MockDBusInterface* MockDBusInterface::ptr = nullptr;
diff --git a/src/test/propertywatchtest.hpp b/src/test/propertywatchtest.hpp
new file mode 100644
index 0000000..f965096
--- /dev/null
+++ b/src/test/propertywatchtest.hpp
@@ -0,0 +1,628 @@
+#pragma once
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <string>
+
+#include "data_types.hpp"
+#include "sdbusplus/bus/match.hpp"
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+
+/** @class CallMethodAndRead
+ *  @brief GMock template member forwarding helper.
+ *
+ *  The code under test calls callMethodAndRead, which is a templated,
+ *  free function.  Enable this under GMock by forwarding calls to it
+ *  to functions that can be mocked.
+ *
+ *  @tparam DBusInterfaceType - The mock object type.
+ *  @tparam Ret - The return type of the method being called.
+ *  @tparam Args - The argument types of the method being called.
+ *
+ *  Specialize to implement new forwards.
+ */
+template <
+    typename DBusInterfaceType,
+    typename Ret,
+    typename ...Args >
+struct CallMethodAndRead
+{
+    static Ret op(
+        DBusInterfaceType& dbus,
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        Args&& ... args)
+    {
+        static_assert(true, "Missing CallMethodAndRead definition.");
+        return Ret();
+    }
+};
+
+/** @brief CallMethodAndRead specialization for
+ *     xyz.openbmc_project.ObjectMapper.GetObject. */
+template <typename DBusInterfaceType>
+struct CallMethodAndRead <
+    DBusInterfaceType,
+    GetObject,
+    const MapperPath&,
+    const std::vector<std::string>& >
+{
+    static GetObject op(
+        DBusInterfaceType& dbus,
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        const MapperPath& objectPath,
+        const std::vector<std::string>& interfaces)
+    {
+        return dbus.mapperGetObject(
+                   busName,
+                   path,
+                   interface,
+                   method,
+                   objectPath,
+                   interfaces);
+    }
+};
+
+/** @brief CallMethodAndRead specialization for
+ *     org.freedesktop.DBus.Properties.GetAll(uint64_t). */
+template <typename DBusInterfaceType>
+struct CallMethodAndRead <
+    DBusInterfaceType,
+    PropertiesChanged<uint64_t>,
+    const std::string& >
+{
+    static PropertiesChanged<uint64_t> op(
+        DBusInterfaceType& dbus,
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        const std::string& propertiesInterface)
+    {
+        return dbus.getPropertiesU64(
+                   busName,
+                   path,
+                   interface,
+                   method,
+                   propertiesInterface);
+    }
+};
+
+/** @brief CallMethodAndRead specialization for
+ *     org.freedesktop.DBus.Properties.GetAll(uint32_t). */
+template <typename DBusInterfaceType>
+struct CallMethodAndRead <
+    DBusInterfaceType,
+    PropertiesChanged<uint32_t>,
+    const std::string& >
+{
+    static PropertiesChanged<uint32_t> op(
+        DBusInterfaceType& dbus,
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        const std::string& propertiesInterface)
+    {
+        return dbus.getPropertiesU32(
+                   busName,
+                   path,
+                   interface,
+                   method,
+                   propertiesInterface);
+    }
+};
+
+/** @brief CallMethodAndRead specialization for
+ *     org.freedesktop.DBus.Properties.GetAll(uint16_t). */
+template <typename DBusInterfaceType>
+struct CallMethodAndRead <
+    DBusInterfaceType,
+    PropertiesChanged<uint16_t>,
+    const std::string& >
+{
+    static PropertiesChanged<uint16_t> op(
+        DBusInterfaceType& dbus,
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        const std::string& propertiesInterface)
+    {
+        return dbus.getPropertiesU16(
+                   busName,
+                   path,
+                   interface,
+                   method,
+                   propertiesInterface);
+    }
+};
+
+/** @brief CallMethodAndRead specialization for
+ *     org.freedesktop.DBus.Properties.GetAll(uint8_t). */
+template <typename DBusInterfaceType>
+struct CallMethodAndRead <
+    DBusInterfaceType,
+    PropertiesChanged<uint8_t>,
+    const std::string& >
+{
+    static PropertiesChanged<uint8_t> op(
+        DBusInterfaceType& dbus,
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        const std::string& propertiesInterface)
+    {
+        return dbus.getPropertiesU8(
+                   busName,
+                   path,
+                   interface,
+                   method,
+                   propertiesInterface);
+    }
+};
+
+/** @brief CallMethodAndRead specialization for
+ *     org.freedesktop.DBus.Properties.GetAll(int64_t). */
+template <typename DBusInterfaceType>
+struct CallMethodAndRead <
+    DBusInterfaceType,
+    PropertiesChanged<int64_t>,
+    const std::string& >
+{
+    static PropertiesChanged<int64_t> op(
+        DBusInterfaceType& dbus,
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        const std::string& propertiesInterface)
+    {
+        return dbus.getPropertiesU64(
+                   busName,
+                   path,
+                   interface,
+                   method,
+                   propertiesInterface);
+    }
+};
+
+/** @brief CallMethodAndRead specialization for
+ *     org.freedesktop.DBus.Properties.GetAll(int32_t). */
+template <typename DBusInterfaceType>
+struct CallMethodAndRead <
+    DBusInterfaceType,
+    PropertiesChanged<int32_t>,
+    const std::string& >
+{
+    static PropertiesChanged<int32_t> op(
+        DBusInterfaceType& dbus,
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        const std::string& propertiesInterface)
+    {
+        return dbus.getPropertiesU32(
+                   busName,
+                   path,
+                   interface,
+                   method,
+                   propertiesInterface);
+    }
+};
+
+/** @brief CallMethodAndRead specialization for
+ *     org.freedesktop.DBus.Properties.GetAll(int16_t). */
+template <typename DBusInterfaceType>
+struct CallMethodAndRead <
+    DBusInterfaceType,
+    PropertiesChanged<int16_t>,
+    const std::string& >
+{
+    static PropertiesChanged<int16_t> op(
+        DBusInterfaceType& dbus,
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        const std::string& propertiesInterface)
+    {
+        return dbus.getPropertiesU16(
+                   busName,
+                   path,
+                   interface,
+                   method,
+                   propertiesInterface);
+    }
+};
+
+/** @brief CallMethodAndRead specialization for
+ *     org.freedesktop.DBus.Properties.GetAll(int8_t). */
+template <typename DBusInterfaceType>
+struct CallMethodAndRead <
+    DBusInterfaceType,
+    PropertiesChanged<int8_t>,
+    const std::string& >
+{
+    static PropertiesChanged<int8_t> op(
+        DBusInterfaceType& dbus,
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        const std::string& propertiesInterface)
+    {
+        return dbus.getPropertiesU8(
+                   busName,
+                   path,
+                   interface,
+                   method,
+                   propertiesInterface);
+    }
+};
+
+/** @brief CallMethodAndRead specialization for
+ *     org.freedesktop.DBus.Properties.GetAll(std::string). */
+template <typename DBusInterfaceType>
+struct CallMethodAndRead <
+    DBusInterfaceType,
+    PropertiesChanged<std::string>,
+    const std::string& >
+{
+    static PropertiesChanged<std::string> op(
+        DBusInterfaceType& dbus,
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        const std::string& propertiesInterface)
+    {
+        return dbus.getPropertiesString(
+                   busName,
+                   path,
+                   interface,
+                   method,
+                   propertiesInterface);
+    }
+};
+
+/** @class MockDBusInterface
+ *  @brief DBus access delegate implementation for the property watch test
+ *  suite.
+ */
+struct MockDBusInterface
+{
+    MOCK_METHOD6(
+        mapperGetObject,
+        GetObject(
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const MapperPath&,
+            const std::vector<std::string>&));
+
+    MOCK_METHOD5(
+        getPropertiesU64,
+        PropertiesChanged<uint64_t>(
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&));
+
+    MOCK_METHOD5(
+        getPropertiesU32,
+        PropertiesChanged<uint32_t>(
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&));
+
+    MOCK_METHOD5(
+        getPropertiesU16,
+        PropertiesChanged<uint16_t>(
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&));
+
+    MOCK_METHOD5(
+        getPropertiesU8,
+        PropertiesChanged<uint8_t>(
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&));
+
+    MOCK_METHOD5(
+        getPropertiesS64,
+        PropertiesChanged<int64_t>(
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&));
+
+    MOCK_METHOD5(
+        getPropertiesS32,
+        PropertiesChanged<int32_t>(
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&));
+
+    MOCK_METHOD5(
+        getPropertiesS16,
+        PropertiesChanged<int16_t>(
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&));
+
+    MOCK_METHOD5(
+        getPropertiesS8,
+        PropertiesChanged<int8_t>(
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&));
+
+    MOCK_METHOD5(
+        getPropertiesString,
+        PropertiesChanged<std::string>(
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&,
+            const std::string&));
+
+    MOCK_METHOD2(
+        fwdAddMatch,
+        void(
+            const std::string&,
+            const sdbusplus::bus::match::match::callback_t&));
+
+    static MockDBusInterface* ptr;
+    static MockDBusInterface& instance()
+    {
+        return *ptr;
+    }
+    static void instance(MockDBusInterface& p)
+    {
+        ptr = &p;
+    }
+
+    /** @brief GMock member template/free function forward. */
+    template <typename Ret, typename ...Args>
+    static auto callMethodAndRead(
+        const std::string& busName,
+        const std::string& path,
+        const std::string& interface,
+        const std::string& method,
+        Args&& ... args)
+    {
+        return CallMethodAndRead <MockDBusInterface, Ret, Args... >::op(
+                   instance(),
+                   busName,
+                   path,
+                   interface,
+                   method,
+                   std::forward<Args>(args)...);
+    }
+
+    /** @brief GMock free function forward. */
+    static auto addMatch(
+        const std::string& match,
+        const sdbusplus::bus::match::match::callback_t& callback)
+    {
+        instance().fwdAddMatch(match, callback);
+    }
+};
+
+/** @class Expect
+ *  @brief Enable use of EXPECT_CALL from a C++ template.
+ */
+template <typename T> struct Expect {};
+
+template <>
+struct Expect<uint64_t>
+{
+    template <typename MockObjType>
+    static auto& getProperties(
+        MockObjType&& mockObj,
+        const std::string& path,
+        const std::string& interface)
+    {
+        return EXPECT_CALL(
+                   std::forward<MockObjType>(mockObj),
+                   getPropertiesU64(
+                       ::testing::_,
+                       path,
+                       "org.freedesktop.DBus.Properties",
+                       "GetAll",
+                       interface));
+    }
+};
+
+template <>
+struct Expect<uint32_t>
+{
+    template <typename MockObjType>
+    static auto& getProperties(
+        MockObjType&& mockObj,
+        const std::string& path,
+        const std::string& interface)
+    {
+        return EXPECT_CALL(
+                   std::forward<MockObjType>(mockObj),
+                   getPropertiesU32(
+                       ::testing::_,
+                       path,
+                       "org.freedesktop.DBus.Properties",
+                       "GetAll",
+                       interface));
+    }
+};
+
+template <>
+struct Expect<uint16_t>
+{
+    template <typename MockObjType>
+    static auto& getProperties(
+        MockObjType&& mockObj,
+        const std::string& path,
+        const std::string& interface)
+    {
+        return EXPECT_CALL(
+                   std::forward<MockObjType>(mockObj),
+                   getPropertiesU16(
+                       ::testing::_,
+                       path,
+                       "org.freedesktop.DBus.Properties",
+                       "GetAll",
+                       interface));
+    }
+};
+
+template <>
+struct Expect<uint8_t>
+{
+    template <typename MockObjType>
+    static auto& getProperties(
+        MockObjType&& mockObj,
+        const std::string& path,
+        const std::string& interface)
+    {
+        return EXPECT_CALL(
+                   std::forward<MockObjType>(mockObj),
+                   getPropertiesU8(
+                       ::testing::_,
+                       path,
+                       "org.freedesktop.DBus.Properties",
+                       "GetAll",
+                       interface));
+    }
+};
+
+template <>
+struct Expect<int64_t>
+{
+    template <typename MockObjType>
+    static auto& getProperties(
+        MockObjType&& mockObj,
+        const std::string& path,
+        const std::string& interface)
+    {
+        return EXPECT_CALL(
+                   std::forward<MockObjType>(mockObj),
+                   getPropertiesS64(
+                       ::testing::_,
+                       path,
+                       "org.freedesktop.DBus.Properties",
+                       "GetAll",
+                       interface));
+    }
+};
+
+template <>
+struct Expect<int32_t>
+{
+    template <typename MockObjType>
+    static auto& getProperties(
+        MockObjType&& mockObj,
+        const std::string& path,
+        const std::string& interface)
+    {
+        return EXPECT_CALL(
+                   std::forward<MockObjType>(mockObj),
+                   getPropertiesS32(
+                       ::testing::_,
+                       path,
+                       "org.freedesktop.DBus.Properties",
+                       "GetAll",
+                       interface));
+    }
+};
+
+template <>
+struct Expect<int16_t>
+{
+    template <typename MockObjType>
+    static auto& getProperties(
+        MockObjType&& mockObj,
+        const std::string& path,
+        const std::string& interface)
+    {
+        return EXPECT_CALL(
+                   std::forward<MockObjType>(mockObj),
+                   getPropertiesS16(
+                       ::testing::_,
+                       path,
+                       "org.freedesktop.DBus.Properties",
+                       "GetAll",
+                       interface));
+    }
+};
+
+template <>
+struct Expect<int8_t>
+{
+    template <typename MockObjType>
+    static auto& getProperties(
+        MockObjType&& mockObj,
+        const std::string& path,
+        const std::string& interface)
+    {
+        return EXPECT_CALL(
+                   std::forward<MockObjType>(mockObj),
+                   getPropertiesS8(
+                       ::testing::_,
+                       path,
+                       "org.freedesktop.DBus.Properties",
+                       "GetAll",
+                       interface));
+    }
+};
+
+template <>
+struct Expect<std::string>
+{
+    template <typename MockObjType>
+    static auto& getProperties(
+        MockObjType&& mockObj,
+        const std::string& path,
+        const std::string& interface)
+    {
+        return EXPECT_CALL(
+                   std::forward<MockObjType>(mockObj),
+                   getPropertiesString(
+                       ::testing::_,
+                       path,
+                       "org.freedesktop.DBus.Properties",
+                       "GetAll",
+                       interface));
+    }
+};
+
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor
diff --git a/src/test/templates/propertywatchgentest.mako.hpp b/src/test/templates/propertywatchgentest.mako.hpp
new file mode 100644
index 0000000..ac4dbee
--- /dev/null
+++ b/src/test/templates/propertywatchgentest.mako.hpp
@@ -0,0 +1,17 @@
+auto storageCount = ${len(instances)};
+
+const std::array<Index, ${len(instancegroups)}> indicies = {{
+% for g in instancegroups:
+    {
+    % for i in g.members:
+        {Index::key_type{${i[0]}, ${i[2]}, ${i[3]}}, ${i[5]}},
+    % endfor
+    },
+% endfor
+}};
+
+const std::array<std::tuple<std::string, size_t>, ${len(watches)}> watches = {{
+% for w in watches:
+    std::tuple<std::string, size_t>{"${w.datatype}", ${w.instances}},
+% endfor
+}};
diff --git a/src/test/yaml/propertywatchgentest/watchone.yaml b/src/test/yaml/propertywatchgentest/watchone.yaml
new file mode 100644
index 0000000..bea2b09
--- /dev/null
+++ b/src/test/yaml/propertywatchgentest/watchone.yaml
@@ -0,0 +1,61 @@
+- name: test path group 1
+  class: group
+  group: path
+  members:
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst1
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst2
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst3
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst4
+
+- name: test path group 2
+  class: group
+  group: path
+  members:
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst3
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst4
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst5
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst6
+
+- name: test property group 1
+  class: group
+  group: property
+  type: string
+  members:
+    - interface: xyz.openbmc_project.Sensor.Iface1
+      meta: property
+      property: Value
+    - interface: xyz.openbmc_project.Sensor.Iface2
+      meta: property
+      property: Value
+
+- name: test property group 2
+  class: group
+  group: property
+  type: uint32
+  members:
+    - interface: xyz.openbmc_project.Sensor.Iface3
+      meta: property
+      property: Value1
+    - interface: xyz.openbmc_project.Sensor.Iface3
+      meta: property
+      property: Value2
+
+- name: test property watch 1
+  class: watch
+  watch: property
+  paths: test path group 1
+  properties: test property group 1
+
+- name: test property watch 2
+  class: watch
+  watch: property
+  paths: test path group 2
+  properties: test property group 2
diff --git a/src/test/yaml/propertywatchgentest/watchtwo.yaml b/src/test/yaml/propertywatchgentest/watchtwo.yaml
new file mode 100644
index 0000000..0f5ebdc
--- /dev/null
+++ b/src/test/yaml/propertywatchgentest/watchtwo.yaml
@@ -0,0 +1,45 @@
+- name: test path group 1
+  class: group
+  group: path
+  members:
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst4
+
+- name: test path group 3
+  class: group
+  group: path
+  members:
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst4
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst6
+
+- name: test property group 1
+  class: group
+  group: property
+  type: int32
+  members:
+    - interface: xyz.openbmc_project.Sensor.Iface1
+      meta: property
+      property: Value
+
+- name: test property group 3
+  class: group
+  group: property
+  type: string
+  members:
+    - interface: xyz.openbmc_project.Sensor.Iface3
+      meta: property
+      property: Value2
+
+- name: test property watch 1
+  class: watch
+  watch: property
+  paths: test path group 1
+  properties: test property group 1
+
+- name: test property watch 3
+  class: watch
+  watch: property
+  paths: test path group 3
+  properties: test property group 3