Enhance notify testcase

Validate signal content rather than doing explicit property
lookups.  This avoids race conditions and additionally validates
that a signal is sent with the correct values.

Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
Change-Id: Iaf13b1dfd68eeaf8ab7e8dc3bf045f824c3c803a
diff --git a/test/test.cpp b/test/test.cpp
index f089fcd..669879d 100644
--- a/test/test.cpp
+++ b/test/test.cpp
@@ -16,6 +16,8 @@
 #include "../manager.hpp"
 #include "../config.h"
 #include <cassert>
+#include <iostream>
+#include <algorithm>
 
 constexpr auto SERVICE = "phosphor.inventory.test";
 constexpr auto INTERFACE = IFACE;
@@ -30,46 +32,142 @@
     return static_cast<void *>(nullptr);
 }
 
+/** @class SignalQueue
+ *  @brief Store DBus signals in a queue.
+ */
+class SignalQueue
+{
+    public:
+    ~SignalQueue() = default;
+    SignalQueue() = delete;
+    SignalQueue(const SignalQueue &) = delete;
+    SignalQueue(SignalQueue &&) = default;
+    SignalQueue& operator=(const SignalQueue &) = delete;
+    SignalQueue& operator=(SignalQueue &&) = default;
+    explicit SignalQueue(const std::string &match) :
+        _bus(sdbusplus::bus::new_default()),
+        _match(_bus, match.c_str(), &callback, this),
+        _next(nullptr)
+    {
+    }
+
+    auto &&pop(unsigned timeout=1000000)
+    {
+        while(timeout > 0 && !_next)
+        {
+            _bus.process_discard();
+            _bus.wait(50000);
+            timeout -= 50000;
+        }
+        return std::move(_next);
+    }
+
+    private:
+    static int callback(sd_bus_message *m, void *context, sd_bus_error *)
+    {
+        auto *me = static_cast<SignalQueue *>(context);
+        sd_bus_message_ref(m);
+        sdbusplus::message::message msg{m};
+        me->_next = std::move(msg);
+        return 0;
+    }
+
+    sdbusplus::bus::bus _bus;
+    sdbusplus::server::match::match _match;
+    sdbusplus::message::message _next;
+};
+
+template <typename ...T>
+using Object = std::map<
+        std::string,
+        std::map<
+            std::string,
+            sdbusplus::message::variant<T...>>>;
+
+using ObjectPath = std::string;
+
+/**@brief Find a subset of interfaces and properties in an object. */
+template <typename ...T>
+auto hasProperties(const Object<T...> &l, const Object<T...> &r)
+{
+    Object<T...> result;
+    std::set_difference(
+            r.cbegin(),
+            r.cend(),
+            l.cbegin(),
+            l.cend(),
+            std::inserter(result, result.end()));
+    return result.empty();
+}
+
 void runTests(phosphor::inventory::manager::Manager &mgr)
 {
+    const std::string root{ROOT};
     auto b = sdbusplus::bus::new_default();
-
-    // make sure the notify method works
+    auto notify = [&]()
     {
-        auto m = b.new_method_call(SERVICE, ROOT, INTERFACE, "Notify");
-        m.append("/foo");
+        return b.new_method_call(
+                SERVICE,
+                ROOT,
+                INTERFACE,
+                "Notify");
+    };
 
-        using var = sdbusplus::message::variant<std::string>;
-        using inner = std::map<std::string, var>;
-        using outer = std::map<std::string, inner>;
+    Object<std::string> obj{
+        {
+            "xyz.openbmc_project.Example.Iface1",
+            {{"ExampleProperty1", "test1"}}
+        },
+        {
+            "xyz.openbmc_project.Example.Iface2",
+            {{"ExampleProperty2", "test2"}}
+        },
+    };
 
-        inner i = {{"ExampleProperty1", "test"}};
-        outer o = {{"xyz.openbmc_project.Example.Iface1", i}};
+    // Make sure the notify method works.
+    {
+        ObjectPath relPath{"/foo"};
+        ObjectPath path{root + relPath};
 
-        m.append(o);
-        auto reply = b.call(m);
-        auto cleanup = sdbusplus::message::message(reply);
-        assert(sd_bus_message_get_errno(reply) == 0);
+        SignalQueue queue(
+                "path='" + root + "',member='InterfacesAdded'");
+
+        auto m = notify();
+        m.append(relPath);
+        m.append(obj);
+        b.call(m);
+
+        auto sig{queue.pop()};
+        assert(sig);
+        ObjectPath signalPath;
+        Object<std::string> signalObject;
+        sig.read(signalPath);
+        assert(path == signalPath);
+        sig.read(signalObject);
+        assert(hasProperties(signalObject, obj));
+        auto moreSignals{queue.pop()};
+        assert(!moreSignals);
     }
 
     mgr.shutdown();
+    std::cout << "Success!" << std::endl;
 }
 
 int main()
 {
     auto mgr = phosphor::inventory::manager::Manager(
-            sdbusplus::bus::new_system(),
+            sdbusplus::bus::new_default(),
             SERVICE, ROOT, INTERFACE);
 
     pthread_t t;
     {
-        pthread_create(&t, NULL, server_thread, &mgr);
+        pthread_create(&t, nullptr, server_thread, &mgr);
     }
 
     runTests(mgr);
 
     // Wait for server thread to exit.
-    pthread_join(t, NULL);
+    pthread_join(t, nullptr);
 
     return 0;
 }