message: Add call_async method

This makes it possible to perform an async method call that is agnostic
to the event loop running the sd_bus state machine.

Change-Id: I32bc0fdf89c44cc6bab1c4622b143d6e06098659
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/test/message/call.cpp b/test/message/call.cpp
new file mode 100644
index 0000000..0ef015f
--- /dev/null
+++ b/test/message/call.cpp
@@ -0,0 +1,102 @@
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+
+#include <chrono>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace sdbusplus
+{
+namespace message
+{
+
+using namespace std::literals::chrono_literals;
+
+std::string globalId;
+
+void setGlobalId(message&& m)
+{
+    m.read(globalId);
+}
+
+message newBusIdReq(bus::bus& b)
+{
+    return b.new_method_call("org.freedesktop.DBus", "/org/freedesktop/DBus",
+                             "org.freedesktop.DBus", "GetId");
+}
+
+std::string syncBusId(bus::bus& b)
+{
+    std::string ret;
+    newBusIdReq(b).call().read(ret);
+    return ret;
+}
+
+TEST(CallAsync, Function)
+{
+    auto b = bus::new_default();
+    globalId = "";
+    while (b.process_discard())
+        ;
+    auto slot = newBusIdReq(b).call_async(setGlobalId);
+    b.wait(1s);
+    b.process_discard();
+    EXPECT_EQ(syncBusId(b), globalId);
+}
+
+TEST(CallAsync, FunctionPointer)
+{
+    auto b = bus::new_default();
+    globalId = "";
+    while (b.process_discard())
+        ;
+    auto slot = newBusIdReq(b).call_async(&setGlobalId);
+    b.wait(1s);
+    b.process_discard();
+    EXPECT_EQ(syncBusId(b), globalId);
+}
+
+TEST(CallAsync, Lambda)
+{
+    auto b = bus::new_default();
+    std::string id;
+    while (b.process_discard())
+        ;
+    auto slot = newBusIdReq(b).call_async([&](message&& m) { m.read(id); });
+    b.wait(1s);
+    b.process_discard();
+    EXPECT_EQ(syncBusId(b), id);
+}
+
+TEST(CallAsync, SlotDrop)
+{
+    auto b = bus::new_default();
+    globalId = "";
+    while (b.process_discard())
+        ;
+    {
+        auto slot = newBusIdReq(b).call_async(setGlobalId);
+    }
+    b.wait(1s);
+    b.process_discard();
+    EXPECT_EQ("", globalId);
+}
+
+TEST(CallAsync, ExceptionCaught)
+{
+    EXPECT_DEATH(
+        [] {
+            auto b = bus::new_bus();
+            while (b.process_discard())
+                ;
+            auto slot = newBusIdReq(b).call_async(
+                [&](message&&) { throw std::runtime_error("testerror"); });
+            b.wait(1s);
+            b.process_discard();
+        }(),
+        "testerror");
+}
+
+} // namespace message
+} // namespace sdbusplus