summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Rauwolf <rauwolf@itestra.de>2013-04-18 16:29:52 +0200
committerPhilip Rauwolf <rauwolf@itestra.de>2013-04-18 16:29:52 +0200
commit0b137b5e253265f3033b0b4fa99b74e340274233 (patch)
tree007a1d0f0b47b7da3acfc6c3ecc0c68e13c2c70a
parent0e1b727ec4b141d88a44067a8994b08294aa4d40 (diff)
downloadgenivi-common-api-dbus-runtime-0b137b5e253265f3033b0b4fa99b74e340274233.tar.gz
Added Unit Tests and Demo Implementation for Mainloops
Change-Id: I51d4f076e22053f32c453f8b70edd29e22149d35
-rw-r--r--Makefile.am10
-rw-r--r--configure.ac28
-rw-r--r--src/CommonAPI/DBus/DBusConnection.h5
-rw-r--r--src/test/DBusMainLoopIntegrationTest.cpp545
-rw-r--r--src/test/DemoMainLoop.h334
5 files changed, 909 insertions, 13 deletions
diff --git a/Makefile.am b/Makefile.am
index e73fb5c..f5e43e1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -103,6 +103,7 @@ TestInterfaceSources = \
src/test/fakeLegacyService/fake/legacy/service/LegacyInterfaceDBusProxy.cpp
check_PROGRAMS = \
+ DBusMainLoopIntegrationTest \
DBusConnectionTest \
DBusServiceRegistryTest \
DBusProxyTest \
@@ -120,9 +121,16 @@ check_PROGRAMS = \
DBusBenchmarkingTest
-TESTS = ${check_PROGRAMS}
+TESTS = ${check_PROGRAMS}
LDADD_FOR_GTEST = libCommonAPI-DBus.la ${GTEST_LIBS} ${LDADD}
+DBusMainLoopIntegrationTest_SOURCES = \
+ src/test/DBusMainLoopIntegrationTest.cpp \
+ ${TestInterfaceSources}
+DBusMainLoopIntegrationTest_CPPFLAGS = ${AM_CPPFLAGS} ${GTEST_CPPFLAGS} ${GLIB_CFLAGS}
+DBusMainLoopIntegrationTest_CXXFLAGS = ${GTEST_CXXFLAGS} ${GLIB_CFLAGS}
+DBusMainLoopIntegrationTest_LDADD = ${LDADD_FOR_GTEST} ${GLIB_LIBS}
+
DBusServiceRegistryTest_SOURCES = \
src/test/DBusServiceRegistryTest.cpp \
${TestInterfaceSources}
diff --git a/configure.ac b/configure.ac
index 2cd0b70..3ab0d8f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -11,9 +11,9 @@ m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_HEADERS([build-aux/config.h])
AC_CONFIG_FILES([
- Makefile
- CommonAPI-DBus.pc
- CommonAPI-DBus-uninstalled.pc])
+ Makefile
+ CommonAPI-DBus.pc
+ CommonAPI-DBus-uninstalled.pc])
AC_CANONICAL_SYSTEM
@@ -33,6 +33,13 @@ LT_INIT
PKG_PROG_PKG_CONFIG
PKG_CHECK_MODULES(COMMONAPI, [CommonAPI >= 0.7])
PKG_CHECK_MODULES(DBUS, [dbus-1 >= 1.4.6])
+PKG_CHECK_MODULES(GLIB, [glib-2.0],
+ [],
+ [
+ echo "WARNING: No glib found, tests will not be compiled!"
+ TESTS_CAN_BE_EXECUTED=false
+ ]
+)
GTEST_MIN_VERSION="1.6.0"
GTEST_URL="http://code.google.com/p/googletest"
@@ -60,8 +67,7 @@ AS_IF([test -f "${GTEST_CONFIG}"],
[]
)
-AM_CONDITIONAL(ENABLE_TESTS, [test -f "${GTEST_CONFIG}"])
-
+AM_CONDITIONAL(ENABLE_TESTS, [test -f "${GTEST_CONFIG}" && ${TESTS_CAN_BE_EXECUTED}])
# Doxygen support
DX_HTML_FEATURE(ON)
@@ -73,17 +79,17 @@ DX_XML_FEATURE(OFF)
DX_PDF_FEATURE(OFF)
DX_PS_FEATURE(OFF)
DX_INIT_DOXYGEN(${PACKAGE_NAME}, doxygen.cfg, doc)
-
+
AC_MSG_RESULT([
$PACKAGE_NAME v$VERSION
enable docs: ${ENABLE_DOCS}
- COMMONAPI_CFLAGS: ${COMMONAPI_CFLAGS}
- COMMONAPI_LIBS: ${COMMONAPI_LIBS}
+ COMMONAPI_CFLAGS: ${COMMONAPI_CFLAGS}
+ COMMONAPI_LIBS: ${COMMONAPI_LIBS}
- DBUS_CFLAGS: ${DBUS_CFLAGS}
- DBUS_LIBS: ${DBUS_LIBS}
+ DBUS_CFLAGS: ${DBUS_CFLAGS}
+ DBUS_LIBS: ${DBUS_LIBS}
GTEST_CONFIG: ${GTEST_CONFIG}
GTEST_CPPFLAGS: ${GTEST_CPPFLAGS}
@@ -97,4 +103,4 @@ AC_MSG_RESULT([
LDFLAGS: ${LDFLAGS}
])
-AC_OUTPUT \ No newline at end of file
+AC_OUTPUT
diff --git a/src/CommonAPI/DBus/DBusConnection.h b/src/CommonAPI/DBus/DBusConnection.h
index 79055ef..81c8cfa 100644
--- a/src/CommonAPI/DBus/DBusConnection.h
+++ b/src/CommonAPI/DBus/DBusConnection.h
@@ -44,7 +44,10 @@ struct WatchContext {
class DBusConnection: public DBusProxyConnection, public std::enable_shared_from_this<DBusConnection> {
public:
enum BusType {
- SESSION = DBUS_BUS_SESSION, SYSTEM = DBUS_BUS_SYSTEM, STARTER = DBUS_BUS_STARTER, WRAPPED
+ SESSION = DBUS_BUS_SESSION,
+ SYSTEM = DBUS_BUS_SYSTEM,
+ STARTER = DBUS_BUS_STARTER,
+ WRAPPED
};
DBusConnection(BusType busType);
diff --git a/src/test/DBusMainLoopIntegrationTest.cpp b/src/test/DBusMainLoopIntegrationTest.cpp
new file mode 100644
index 0000000..c39130c
--- /dev/null
+++ b/src/test/DBusMainLoopIntegrationTest.cpp
@@ -0,0 +1,545 @@
+/* Copyright (C) 2013 BMW Group
+ * Author: Manfred Bathelt (manfred.bathelt@bmw.de)
+ * Author: Juergen Gehring (juergen.gehring@bmw.de)
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <gtest/gtest.h>
+
+#include <cassert>
+#include <cstdint>
+#include <iostream>
+#include <functional>
+#include <memory>
+#include <stdint.h>
+#include <string>
+#include <utility>
+#include <tuple>
+#include <type_traits>
+#include <glib.h>
+
+#include <CommonAPI/types.h>
+#include <CommonAPI/AttributeExtension.h>
+#include <CommonAPI/Runtime.h>
+
+#include <CommonAPI/DBus/DBusConnection.h>
+#include <CommonAPI/DBus/DBusProxy.h>
+#include <CommonAPI/DBus/DBusRuntime.h>
+
+#include "DBusTestUtils.h"
+#include "DemoMainLoop.h"
+
+#include "commonapi/tests/PredefinedTypeCollection.h"
+#include "commonapi/tests/DerivedTypeCollection.h"
+#include "commonapi/tests/TestInterfaceProxy.h"
+#include "commonapi/tests/TestInterfaceStubDefault.h"
+#include "commonapi/tests/TestInterfaceDBusStubAdapter.h"
+
+#include "commonapi/tests/TestInterfaceDBusProxy.h"
+
+
+const std::string testAddress1 = "local:my.first.test:commonapi.address.one";
+const std::string testAddress2 = "local:my.second.test:commonapi.address.two";
+const std::string testAddress3 = "local:my.third.test:commonapi.address.three";
+const std::string testAddress4 = "local:my.fourth.test:commonapi.address.four";
+const std::string testAddress5 = "local:my.fifth.test:commonapi.address.five";
+const std::string testAddress6 = "local:my.sixth.test:commonapi.address.six";
+const std::string testAddress7 = "local:my.seventh.test:commonapi.address.seven";
+
+//####################################################################################################################
+
+class DBusBasicMainLoopTest: public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+
+TEST_F(DBusBasicMainLoopTest, MainloopContextCanBeCreated) {
+ std::shared_ptr<CommonAPI::Runtime> runtime = CommonAPI::Runtime::load();
+ ASSERT_TRUE((bool) runtime);
+
+ std::shared_ptr<CommonAPI::MainLoopContext> context = runtime->getNewMainLoopContext();
+ ASSERT_TRUE((bool) context);
+}
+
+
+TEST_F(DBusBasicMainLoopTest, SeveralMainloopContextsCanBeCreated) {
+ std::shared_ptr<CommonAPI::Runtime> runtime = CommonAPI::Runtime::load();
+ ASSERT_TRUE((bool)runtime);
+
+ std::shared_ptr<CommonAPI::MainLoopContext> context1 = runtime->getNewMainLoopContext();
+ ASSERT_TRUE((bool) context1);
+ std::shared_ptr<CommonAPI::MainLoopContext> context2 = runtime->getNewMainLoopContext();
+ ASSERT_TRUE((bool) context2);
+ std::shared_ptr<CommonAPI::MainLoopContext> context3 = runtime->getNewMainLoopContext();
+ ASSERT_TRUE((bool) context3);
+
+ ASSERT_NE(context1, context2);
+ ASSERT_NE(context1, context3);
+ ASSERT_NE(context2, context3);
+}
+
+struct TestSource: public CommonAPI::DispatchSource {
+ TestSource(const std::string value, std::string& result): value_(value), result_(result) {}
+
+ bool prepare(int64_t& timeout) {
+ return true;
+ }
+ bool check() {
+ return true;
+ }
+ bool dispatch() {
+ result_.append(value_);
+ return false;
+ }
+
+ private:
+ std::string value_;
+ std::string& result_;
+};
+
+TEST_F(DBusBasicMainLoopTest, PrioritiesAreHandledCorrectlyInDemoMainloop) {
+ std::shared_ptr<CommonAPI::Runtime> runtime = CommonAPI::Runtime::load();
+ ASSERT_TRUE((bool) runtime);
+
+ std::shared_ptr<CommonAPI::MainLoopContext> context = runtime->getNewMainLoopContext();
+ ASSERT_TRUE((bool) context);
+
+ auto mainLoop = new CommonAPI::MainLoop(context);
+
+ std::string result = "";
+
+ TestSource* testSource1Default = new TestSource("A", result);
+ TestSource* testSource2Default = new TestSource("B", result);
+ TestSource* testSource1High = new TestSource("C", result);
+ TestSource* testSource1Low = new TestSource("D", result);
+ TestSource* testSource1VeryHigh = new TestSource("E", result);
+ context->registerDispatchSource(testSource1Default);
+ context->registerDispatchSource(testSource2Default, CommonAPI::DispatchPriority::DEFAULT);
+ context->registerDispatchSource(testSource1High, CommonAPI::DispatchPriority::HIGH);
+ context->registerDispatchSource(testSource1Low, CommonAPI::DispatchPriority::LOW);
+ context->registerDispatchSource(testSource1VeryHigh, CommonAPI::DispatchPriority::VERY_HIGH);
+
+ mainLoop->doSingleIteration(CommonAPI::TIMEOUT_INFINITE);
+
+ std::string reference("ECABD");
+ ASSERT_EQ(reference, result);
+}
+
+
+//####################################################################################################################
+
+class DBusMainLoopTest: public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ runtime_ = CommonAPI::Runtime::load();
+ ASSERT_TRUE((bool) runtime_);
+
+ context_ = runtime_->getNewMainLoopContext();
+ ASSERT_TRUE((bool) context_);
+ mainLoop_ = new CommonAPI::MainLoop(context_);
+
+ mainloopFactory_ = runtime_->createFactory(context_);
+ ASSERT_TRUE((bool) mainloopFactory_);
+ standardFactory_ = runtime_->createFactory();
+ ASSERT_TRUE((bool) standardFactory_);
+ }
+
+ virtual void TearDown() {
+ delete mainLoop_;
+ }
+
+ std::shared_ptr<CommonAPI::Runtime> runtime_;
+ std::shared_ptr<CommonAPI::MainLoopContext> context_;
+ std::shared_ptr<CommonAPI::Factory> mainloopFactory_;
+ std::shared_ptr<CommonAPI::Factory> standardFactory_;
+ CommonAPI::MainLoop* mainLoop_;
+};
+
+
+TEST_F(DBusMainLoopTest, ServiceInDemoMainloopCanBeAddressed) {
+ std::shared_ptr<commonapi::tests::TestInterfaceStubDefault> stub = std::make_shared<
+ commonapi::tests::TestInterfaceStubDefault>();
+ ASSERT_TRUE(mainloopFactory_->registerService(stub, testAddress1));
+
+ auto proxy = standardFactory_->buildProxy<commonapi::tests::TestInterfaceProxy>(testAddress1);
+ ASSERT_TRUE((bool) proxy);
+
+ while (!proxy->isAvailable()) {
+ mainLoop_->doSingleIteration(50000);
+ }
+
+ uint32_t uint32Value = 42;
+ std::string stringValue = "Hai :)";
+
+ std::future<CommonAPI::CallStatus> futureStatus = proxy->testVoidPredefinedTypeMethodAsync(
+ uint32Value,
+ stringValue,
+ [&] (const CommonAPI::CallStatus& status) {
+ EXPECT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(status));
+ mainLoop_->stop();
+ }
+ );
+
+ mainLoop_->run();
+
+ ASSERT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(futureStatus.get()));
+
+ mainloopFactory_->unregisterService(testAddress1);
+}
+
+
+TEST_F(DBusMainLoopTest, ProxyInDemoMainloopCanCallMethods) {
+ std::shared_ptr<commonapi::tests::TestInterfaceStubDefault> stub = std::make_shared<
+ commonapi::tests::TestInterfaceStubDefault>();
+ ASSERT_TRUE(standardFactory_->registerService(stub, testAddress2));
+
+ usleep(500000);
+
+ auto proxy = mainloopFactory_->buildProxy<commonapi::tests::TestInterfaceProxy>(testAddress2);
+ ASSERT_TRUE((bool) proxy);
+
+ while (!proxy->isAvailable()) {
+ mainLoop_->doSingleIteration();
+ usleep(50000);
+ }
+
+ uint32_t uint32Value = 42;
+ std::string stringValue = "Hai :)";
+
+ std::future<CommonAPI::CallStatus> futureStatus = proxy->testVoidPredefinedTypeMethodAsync(
+ uint32Value,
+ stringValue,
+ [&] (const CommonAPI::CallStatus& status) {
+ EXPECT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(status));
+ mainLoop_->stop();
+ }
+ );
+
+ mainLoop_->run();
+
+ ASSERT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(futureStatus.get()));
+
+ standardFactory_->unregisterService(testAddress2);
+}
+
+TEST_F(DBusMainLoopTest, ProxyAndServiceInSameDemoMainloopCanCommunicate) {
+ std::shared_ptr<commonapi::tests::TestInterfaceStubDefault> stub = std::make_shared<
+ commonapi::tests::TestInterfaceStubDefault>();
+ ASSERT_TRUE(mainloopFactory_->registerService(stub, testAddress4));
+
+ auto proxy = mainloopFactory_->buildProxy<commonapi::tests::TestInterfaceProxy>(testAddress4);
+ ASSERT_TRUE((bool) proxy);
+
+ while (!proxy->isAvailable()) {
+ mainLoop_->doSingleIteration();
+ usleep(50000);
+ }
+
+ uint32_t uint32Value = 42;
+ std::string stringValue = "Hai :)";
+ bool running = true;
+
+ std::future<CommonAPI::CallStatus> futureStatus = proxy->testVoidPredefinedTypeMethodAsync(
+ uint32Value,
+ stringValue,
+ [&] (const CommonAPI::CallStatus& status) {
+ EXPECT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(status));
+ mainLoop_->stop();
+ }
+ );
+
+ mainLoop_->run();
+
+ ASSERT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(futureStatus.get()));
+}
+
+TEST_F(DBusMainLoopTest, DemoMainloopClientsHandleNonavailableServices) {
+ auto proxy = mainloopFactory_->buildProxy<commonapi::tests::TestInterfaceProxy>(testAddress3);
+ ASSERT_TRUE((bool) proxy);
+
+ uint32_t uint32Value = 42;
+ std::string stringValue = "Hai :)";
+
+ std::future<CommonAPI::CallStatus> futureStatus = proxy->testVoidPredefinedTypeMethodAsync(
+ uint32Value,
+ stringValue,
+ [&] (const CommonAPI::CallStatus& status) {
+ //Will never be called, @see DBusProxyAsyncCallbackHandler
+ EXPECT_EQ(toString(CommonAPI::CallStatus::NOT_AVAILABLE), toString(status));
+ }
+ );
+
+ ASSERT_EQ(toString(CommonAPI::CallStatus::NOT_AVAILABLE), toString(futureStatus.get()));
+}
+
+//##################################################################################################
+
+class GDispatchWrapper: public GSource {
+ public:
+ GDispatchWrapper(CommonAPI::DispatchSource* dispatchSource): dispatchSource_(dispatchSource) {}
+ CommonAPI::DispatchSource* dispatchSource_;
+};
+
+gboolean dispatchPrepare(GSource* source, gint* timeout) {
+ int64_t eventTimeout;
+ return static_cast<GDispatchWrapper*>(source)->dispatchSource_->prepare(eventTimeout);
+}
+
+gboolean dispatchCheck(GSource* source) {
+ return static_cast<GDispatchWrapper*>(source)->dispatchSource_->check();
+}
+
+gboolean dispatchExecute(GSource* source, GSourceFunc callback, gpointer userData) {
+ static_cast<GDispatchWrapper*>(source)->dispatchSource_->dispatch();
+ return true;
+}
+
+gboolean gWatchDispatcher(GIOChannel *source, GIOCondition condition, gpointer userData) {
+ CommonAPI::Watch* watch = static_cast<CommonAPI::Watch*>(userData);
+ watch->dispatch(condition);
+ return true;
+}
+
+gboolean gTimeoutDispatcher(void* userData) {
+ return static_cast<CommonAPI::DispatchSource*>(userData)->dispatch();
+}
+
+
+static GSourceFuncs standardGLibSourceCallbackFuncs = {
+ dispatchPrepare,
+ dispatchCheck,
+ dispatchExecute,
+ NULL
+};
+
+
+class DBusInGLibMainLoopTest: public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ running_ = true;
+
+ std::shared_ptr<CommonAPI::Runtime> runtime_ = CommonAPI::Runtime::load();
+ ASSERT_TRUE((bool) runtime_);
+
+ context_ = runtime_->getNewMainLoopContext();
+ ASSERT_TRUE((bool) context_);
+
+ doSubscriptions();
+
+ mainloopFactory_ = runtime_->createFactory(context_);
+ ASSERT_TRUE((bool) mainloopFactory_);
+ standardFactory_ = runtime_->createFactory();
+ ASSERT_TRUE((bool) standardFactory_);
+ }
+
+ virtual void TearDown() {
+ }
+
+ void doSubscriptions() {
+ context_->subscribeForTimeouts(
+ std::bind(&DBusInGLibMainLoopTest::timeoutAddedCallback, this, std::placeholders::_1, std::placeholders::_2),
+ std::bind(&DBusInGLibMainLoopTest::timeoutRemovedCallback, this, std::placeholders::_1)
+ );
+
+ context_->subscribeForWatches(
+ std::bind(&DBusInGLibMainLoopTest::watchAddedCallback, this, std::placeholders::_1, std::placeholders::_2),
+ std::bind(&DBusInGLibMainLoopTest::watchRemovedCallback, this, std::placeholders::_1)
+ );
+
+ context_->subscribeForWakeupEvents(
+ std::bind(&DBusInGLibMainLoopTest::wakeupMain, this)
+ );
+ }
+
+
+ public:
+ std::shared_ptr<CommonAPI::MainLoopContext> context_;
+ std::shared_ptr<CommonAPI::Factory> mainloopFactory_;
+ std::shared_ptr<CommonAPI::Factory> standardFactory_;
+ bool running_;
+ static constexpr bool mayBlock_ = true;
+
+ std::map<CommonAPI::DispatchSource*, GSource*> gSourceMappings;
+
+ GIOChannel* dbusChannel_ = NULL;
+
+ void watchAddedCallback(CommonAPI::Watch* watch, const CommonAPI::DispatchPriority dispatchPriority) {
+ const pollfd& fileDesc = watch->getAssociatedFileDescriptor();
+ dbusChannel_ = g_io_channel_unix_new(fileDesc.fd);
+
+ GSource* gWatch = g_io_create_watch(dbusChannel_, static_cast<GIOCondition>(fileDesc.events));
+ g_source_set_callback(gWatch, reinterpret_cast<GSourceFunc>(&gWatchDispatcher), watch, NULL);
+
+ const auto& dependentSources = watch->getDependentDispatchSources();
+ for (auto dependentSourceIterator = dependentSources.begin();
+ dependentSourceIterator != dependentSources.end();
+ dependentSourceIterator++) {
+ GSource* gDispatchSource = g_source_new(&standardGLibSourceCallbackFuncs, sizeof(GDispatchWrapper));
+ static_cast<GDispatchWrapper*>(gDispatchSource)->dispatchSource_ = *dependentSourceIterator;
+
+ g_source_add_child_source(gWatch, gDispatchSource);
+
+ gSourceMappings.insert( {*dependentSourceIterator, gDispatchSource} );
+ }
+ g_source_attach(gWatch, NULL);
+ }
+
+ void watchRemovedCallback(CommonAPI::Watch* watch) {
+ g_source_remove_by_user_data(watch);
+
+ if(dbusChannel_) {
+ g_io_channel_unref(dbusChannel_);
+ dbusChannel_ = NULL;
+ }
+
+ const auto& dependentSources = watch->getDependentDispatchSources();
+ for (auto dependentSourceIterator = dependentSources.begin();
+ dependentSourceIterator != dependentSources.end();
+ dependentSourceIterator++) {
+ GSource* gDispatchSource = g_source_new(&standardGLibSourceCallbackFuncs, sizeof(GDispatchWrapper));
+ GSource* gSource = gSourceMappings.find(*dependentSourceIterator)->second;
+ g_source_destroy(gSource);
+ g_source_unref(gSource);
+ }
+ }
+
+ void timeoutAddedCallback(CommonAPI::Timeout* commonApiTimeoutSource, const CommonAPI::DispatchPriority dispatchPriority) {
+ GSource* gTimeoutSource = g_timeout_source_new(commonApiTimeoutSource->getTimeoutInterval());
+ g_source_set_callback(gTimeoutSource, &gTimeoutDispatcher, commonApiTimeoutSource, NULL);
+ g_source_attach(gTimeoutSource, NULL);
+ }
+
+ void timeoutRemovedCallback(CommonAPI::Timeout* timeout) {
+ g_source_remove_by_user_data(timeout);
+ }
+
+ void wakeupMain() {
+ g_main_context_wakeup(NULL);
+ }
+};
+
+
+TEST_F(DBusInGLibMainLoopTest, ProxyInGLibMainloopCanCallMethods) {
+ auto proxy = mainloopFactory_->buildProxy<commonapi::tests::TestInterfaceProxy>(testAddress5);
+ ASSERT_TRUE((bool) proxy);
+
+ std::shared_ptr<commonapi::tests::TestInterfaceStubDefault> stub = std::make_shared<
+ commonapi::tests::TestInterfaceStubDefault>();
+ ASSERT_TRUE(standardFactory_->registerService(stub, testAddress5));
+
+ while(!proxy->isAvailable()) {
+ g_main_context_iteration(NULL, mayBlock_);
+ usleep(50000);
+ }
+
+ uint32_t uint32Value = 24;
+ std::string stringValue = "Hai :)";
+
+ std::future<CommonAPI::CallStatus> futureStatus = proxy->testVoidPredefinedTypeMethodAsync(
+ uint32Value,
+ stringValue,
+ [&] (const CommonAPI::CallStatus& status) {
+ EXPECT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(status));
+ running_ = false;
+ }
+ );
+
+ while(running_) {
+ g_main_context_iteration(NULL, mayBlock_);
+ usleep(50000);
+ }
+
+ ASSERT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(futureStatus.get()));
+
+ standardFactory_->unregisterService(testAddress5);
+}
+
+
+TEST_F(DBusInGLibMainLoopTest, ServiceInGLibMainloopCanBeAddressed) {
+ auto proxy = standardFactory_->buildProxy<commonapi::tests::TestInterfaceProxy>(testAddress6);
+ ASSERT_TRUE((bool) proxy);
+
+ std::shared_ptr<commonapi::tests::TestInterfaceStubDefault> stub = std::make_shared<
+ commonapi::tests::TestInterfaceStubDefault>();
+ ASSERT_TRUE(mainloopFactory_->registerService(stub, testAddress6));
+
+ uint32_t uint32Value = 42;
+ std::string stringValue = "Ciao (:";
+
+ while(!proxy->isAvailable()) {
+ g_main_context_iteration(NULL, mayBlock_);
+ usleep(50000);
+ }
+
+ std::future<CommonAPI::CallStatus> futureStatus = proxy->testVoidPredefinedTypeMethodAsync(
+ uint32Value,
+ stringValue,
+ [&] (const CommonAPI::CallStatus& status) {
+ EXPECT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(status));
+ running_ = false;
+ //Wakeup needed as the service will be in a poll-block when the client
+ //call returns, and no other timeout is present to get him out of there.
+ g_main_context_wakeup(NULL);
+ }
+ );
+
+ while(running_) {
+ g_main_context_iteration(NULL, mayBlock_);
+ usleep(50000);
+ }
+
+ ASSERT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(futureStatus.get()));
+
+ mainloopFactory_->unregisterService(testAddress6);
+}
+
+
+TEST_F(DBusInGLibMainLoopTest, ProxyAndServiceInSameGlibMainloopCanCommunicate) {
+ auto proxy = mainloopFactory_->buildProxy<commonapi::tests::TestInterfaceProxy>(testAddress7);
+ ASSERT_TRUE((bool) proxy);
+
+ std::shared_ptr<commonapi::tests::TestInterfaceStubDefault> stub = std::make_shared<
+ commonapi::tests::TestInterfaceStubDefault>();
+ ASSERT_TRUE(mainloopFactory_->registerService(stub, testAddress7));
+
+ uint32_t uint32Value = 42;
+ std::string stringValue = "Ciao (:";
+
+ while(!proxy->isAvailable()) {
+ g_main_context_iteration(NULL, mayBlock_);
+ usleep(50000);
+ }
+
+ std::future<CommonAPI::CallStatus> futureStatus = proxy->testVoidPredefinedTypeMethodAsync(
+ uint32Value,
+ stringValue,
+ [&] (const CommonAPI::CallStatus& status) {
+ EXPECT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(status));
+ running_ = false;
+ //Wakeup needed as the service will be in a poll-block when the client
+ //call returns, and no other timeout is present to get him out of there.
+ g_main_context_wakeup(NULL);
+ }
+ );
+
+ while(running_) {
+ g_main_context_iteration(NULL, mayBlock_);
+ usleep(50000);
+ }
+
+ ASSERT_EQ(toString(CommonAPI::CallStatus::SUCCESS), toString(futureStatus.get()));
+
+ mainloopFactory_->unregisterService(testAddress7);
+}
+
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/src/test/DemoMainLoop.h b/src/test/DemoMainLoop.h
new file mode 100644
index 0000000..1db5bca
--- /dev/null
+++ b/src/test/DemoMainLoop.h
@@ -0,0 +1,334 @@
+/* Copyright (C) 2013 BMW Group
+ * Author: Manfred Bathelt (manfred.bathelt@bmw.de)
+ * Author: Juergen Gehring (juergen.gehring@bmw.de)
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DEMO_MAIN_LOOP_H_
+#define DEMO_MAIN_LOOP_H_
+
+
+#include <CommonAPI/MainLoopContext.h>
+
+#include <vector>
+#include <set>
+#include <map>
+#include <poll.h>
+#include <unistd.h>
+#include <sys/eventfd.h>
+#include <cassert>
+
+
+namespace CommonAPI {
+
+class MainLoop {
+ public:
+ MainLoop() = delete;
+ MainLoop(const MainLoop&) = delete;
+ MainLoop& operator=(const MainLoop&) = delete;
+ MainLoop(MainLoop&&) = delete;
+ MainLoop& operator=(MainLoop&&) = delete;
+
+ explicit MainLoop(std::shared_ptr<MainLoopContext> context) :
+ context_(context), currentMinimalTimeoutInterval_(TIMEOUT_INFINITE), running_(false), breakLoop_(false) {
+ wakeFd_.fd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK);
+ wakeFd_.events = POLLIN | POLLOUT | POLLERR;
+
+ assert(wakeFd_.fd != -1);
+ registerFileDescriptor(wakeFd_);
+
+ dispatchSourceListenerSubscription_ = context_->subscribeForDispatchSources(
+ std::bind(&CommonAPI::MainLoop::registerDispatchSource, this, std::placeholders::_1, std::placeholders::_2),
+ std::bind(&CommonAPI::MainLoop::deregisterDispatchSource, this, std::placeholders::_1));
+ watchListenerSubscription_ = context_->subscribeForWatches(
+ std::bind(&CommonAPI::MainLoop::registerWatch, this, std::placeholders::_1, std::placeholders::_2),
+ std::bind(&CommonAPI::MainLoop::deregisterWatch, this, std::placeholders::_1));
+ timeoutSourceListenerSubscription_ = context_->subscribeForTimeouts(
+ std::bind(&CommonAPI::MainLoop::registerTimeout, this, std::placeholders::_1, std::placeholders::_2),
+ std::bind(&CommonAPI::MainLoop::deregisterTimeout, this, std::placeholders::_1));
+ wakeupListenerSubscription_ = context_->subscribeForWakeupEvents(
+ std::bind(&CommonAPI::MainLoop::wakeup, this));
+ }
+
+ ~MainLoop() {
+ deregisterFileDescriptor(wakeFd_);
+
+ context_->unsubscribeForDispatchSources(dispatchSourceListenerSubscription_);
+ context_->unsubscribeForWatches(watchListenerSubscription_);
+ context_->unsubscribeForTimeouts(timeoutSourceListenerSubscription_);
+ context_->unsubscribeForWakeupEvents(wakeupListenerSubscription_);
+
+ close(wakeFd_.fd);
+ }
+
+ /**
+ * The given timeout will be overridden if a timeout-event is present that defines an earlier ready time.
+ */
+ void run(const int64_t& timeoutInterval = TIMEOUT_INFINITE) {
+ running_ = true;
+ while(running_) {
+ doSingleIteration(timeoutInterval);
+ }
+ }
+
+ void stop() {
+ running_ = false;
+ wakeup();
+ }
+
+ /**
+ * \brief Executes a single cycle of the mainloop.
+ *
+ * Subsequently calls prepare(), poll(), check() and, if necessary, dispatch().
+ * The given timeout (milliseconds) represents the maximum time
+ * this iteration will remain in the poll state. All other steps
+ * are handled in a non-blocking way. Note however that a source
+ * might claim to have infinite amounts of data to dispatch.
+ * This demo-implementation of a Mainloop will dispatch a source
+ * until it no longer claims to have data to dispatch.
+ * Dispatch will not be called if no sources, watches and timeouts
+ * claim to be ready during the check()-phase.
+ *
+ * @param timeout The maximum poll-timeout for this iteration.
+ */
+ void doSingleIteration(const int64_t& timeout = TIMEOUT_INFINITE) {
+ prepare(timeout);
+ poll();
+ if(check()) {
+ dispatch();
+ }
+ }
+
+ /*
+ * The given timeout is a maximum timeout in ms, measured from the current time in the future
+ * (a value of 0 means "no timeout"). It will be overridden if a timeout-event is present
+ * that defines an earlier ready time.
+ */
+ void prepare(const int64_t& timeout = TIMEOUT_INFINITE) {
+ currentMinimalTimeoutInterval_ = timeout;
+
+ for (auto dispatchSourceIterator = registeredDispatchSources_.begin();
+ dispatchSourceIterator != registeredDispatchSources_.end();
+ dispatchSourceIterator++) {
+
+ int64_t dispatchTimeout = TIMEOUT_INFINITE;
+ if(dispatchSourceIterator->second->prepare(dispatchTimeout)) {
+ sourcesToDispatch_.insert(*dispatchSourceIterator);
+ } else if (dispatchTimeout < currentMinimalTimeoutInterval_) {
+ currentMinimalTimeoutInterval_ = dispatchTimeout;
+ }
+ }
+
+ int64_t currentContextTime = getCurrentTimeInMs();
+
+ for (auto timeoutPriorityRange = registeredTimeouts_.begin();
+ timeoutPriorityRange != registeredTimeouts_.end();
+ timeoutPriorityRange++) {
+
+ int64_t intervalToReady = timeoutPriorityRange->second->getReadyTime() - currentContextTime;
+
+ if (intervalToReady <= 0) {
+ timeoutsToDispatch_.insert(*timeoutPriorityRange);
+ currentMinimalTimeoutInterval_ = TIMEOUT_NONE;
+ } else if (intervalToReady < currentMinimalTimeoutInterval_) {
+ currentMinimalTimeoutInterval_ = intervalToReady;
+ }
+ }
+ }
+
+ void poll() {
+ for (auto fileDescriptor = managedFileDescriptors_.begin() + 1; fileDescriptor != managedFileDescriptors_.end(); ++fileDescriptor) {
+ (*fileDescriptor).revents = 0;
+ }
+
+ auto numReadyFileDescriptors = ::poll(&(managedFileDescriptors_[0]), managedFileDescriptors_.size(), currentMinimalTimeoutInterval_);
+
+ // If no FileDescriptors are ready, poll returned because of a timeout that has expired.
+ // The only case in which this is not the reason is when the timeout handed in "prepare"
+ // expired before any other timeouts.
+ if(!numReadyFileDescriptors) {
+ int64_t currentContextTime = getCurrentTimeInMs();
+
+ for (auto timeoutPriorityRange = registeredTimeouts_.begin();
+ timeoutPriorityRange != registeredTimeouts_.end();
+ timeoutPriorityRange++) {
+
+ int64_t intervalToReady = timeoutPriorityRange->second->getReadyTime() - currentContextTime;
+
+ if (intervalToReady <= 0) {
+ timeoutsToDispatch_.insert(*timeoutPriorityRange);
+ }
+ }
+ }
+
+ if (wakeFd_.revents) {
+ acknowledgeWakeup();
+ }
+ }
+
+ bool check() {
+ //The first file descriptor always is the loop's wakeup-descriptor. All others need to be linked to a watch.
+ for (auto fileDescriptor = managedFileDescriptors_.begin() + 1; fileDescriptor != managedFileDescriptors_.end(); ++fileDescriptor) {
+ for(auto registeredWatchIterator = registeredWatches_.begin();
+ registeredWatchIterator != registeredWatches_.end();
+ registeredWatchIterator++) {
+ const auto& correspondingWatchPriority = registeredWatchIterator->first;
+ const auto& correspondingWatchPair = registeredWatchIterator->second;
+
+ if (std::get<0>(correspondingWatchPair) == fileDescriptor->fd && fileDescriptor->revents) {
+ watchesToDispatch_.insert( { correspondingWatchPriority, {std::get<1>(correspondingWatchPair), fileDescriptor->revents} } );
+ }
+ }
+ }
+
+ for(auto dispatchSourceIterator = registeredDispatchSources_.begin(); dispatchSourceIterator != registeredDispatchSources_.end(); ++dispatchSourceIterator) {
+ if((std::get<1>(*dispatchSourceIterator))->check()) {
+ sourcesToDispatch_.insert( {std::get<0>(*dispatchSourceIterator), std::get<1>(*dispatchSourceIterator)});
+ }
+ }
+
+ return !timeoutsToDispatch_.empty() || !watchesToDispatch_.empty() || !sourcesToDispatch_.empty();
+ }
+
+ void dispatch() {
+ for (auto timeoutIterator = timeoutsToDispatch_.begin();
+ timeoutIterator != timeoutsToDispatch_.end();
+ timeoutIterator++) {
+ std::get<1>(*timeoutIterator)->dispatch();
+ }
+
+ for (auto watchIterator = watchesToDispatch_.begin();
+ watchIterator != watchesToDispatch_.end();
+ watchIterator++) {
+ Watch* watch = std::get<0>(watchIterator->second);
+ const unsigned int flags = std::get<1>(watchIterator->second);
+ watch->dispatch(flags);
+
+ const auto& dependentSources = watch->getDependentDispatchSources();
+ for (auto dependentSourceIterator = dependentSources.begin();
+ dependentSourceIterator != dependentSources.end();
+ dependentSourceIterator++) {
+ if((*dependentSourceIterator)->check()) {
+ sourcesToDispatch_.insert( {watchIterator->first, *dependentSourceIterator} );
+ }
+ }
+ }
+
+ breakLoop_ = false;
+ for (auto dispatchSourceIterator = sourcesToDispatch_.begin();
+ dispatchSourceIterator != sourcesToDispatch_.end() && !breakLoop_;
+ dispatchSourceIterator++) {
+
+ while(std::get<1>(*dispatchSourceIterator)->dispatch());
+ }
+
+ timeoutsToDispatch_.clear();
+ sourcesToDispatch_.clear();
+ watchesToDispatch_.clear();
+ }
+
+ void wakeup() {
+ uint32_t wake = 1;
+ ::write(wakeFd_.fd, &wake, sizeof(uint32_t));
+ }
+
+ private:
+ void registerFileDescriptor(const pollfd& fileDescriptor) {
+ managedFileDescriptors_.push_back(fileDescriptor);
+ }
+
+ void deregisterFileDescriptor(const pollfd& fileDescriptor) {
+ for (auto it = managedFileDescriptors_.begin(); it != managedFileDescriptors_.end(); it++) {
+ if ((*it).fd == fileDescriptor.fd) {
+ managedFileDescriptors_.erase(it);
+ break;
+ }
+ }
+ }
+
+ void registerDispatchSource(DispatchSource* dispatchSource, const DispatchPriority dispatchPriority) {
+ registeredDispatchSources_.insert( {dispatchPriority, dispatchSource} );
+ }
+
+ void deregisterDispatchSource(DispatchSource* dispatchSource) {
+ for(auto dispatchSourceIterator = registeredDispatchSources_.begin();
+ dispatchSourceIterator != registeredDispatchSources_.end();
+ dispatchSourceIterator++) {
+
+ if(dispatchSourceIterator->second == dispatchSource) {
+ registeredDispatchSources_.erase(dispatchSourceIterator);
+ break;
+ }
+ }
+ breakLoop_ = true;
+ }
+
+ void registerWatch(Watch* watch, const DispatchPriority dispatchPriority) {
+ registerFileDescriptor(watch->getAssociatedFileDescriptor());
+ registeredWatches_.insert( { dispatchPriority, {watch->getAssociatedFileDescriptor().fd, watch} } );
+ }
+
+ void deregisterWatch(Watch* watch) {
+ deregisterFileDescriptor(watch->getAssociatedFileDescriptor());
+
+ for(auto watchIterator = registeredWatches_.begin();
+ watchIterator != registeredWatches_.end();
+ watchIterator++) {
+
+ if(watchIterator->second.first == watch->getAssociatedFileDescriptor().fd) {
+ registeredWatches_.erase(watchIterator);
+ break;
+ }
+ }
+ }
+
+ void registerTimeout(Timeout* timeout, const DispatchPriority dispatchPriority) {
+ registeredTimeouts_.insert( {dispatchPriority, timeout} );
+ }
+
+ void deregisterTimeout(Timeout* timeout) {
+ for(auto timeoutIterator = registeredTimeouts_.begin();
+ timeoutIterator != registeredTimeouts_.end();
+ timeoutIterator++) {
+
+ if(timeoutIterator->second == timeout) {
+ registeredTimeouts_.erase(timeoutIterator);
+ break;
+ }
+ }
+ }
+
+ void acknowledgeWakeup() {
+ uint32_t buffer;
+ while (::read(wakeFd_.fd, &buffer, sizeof(uint32_t)) == sizeof(buffer));
+ }
+
+ std::shared_ptr<MainLoopContext> context_;
+
+ std::vector<pollfd> managedFileDescriptors_;
+
+ std::multimap<DispatchPriority, DispatchSource*> registeredDispatchSources_;
+ std::multimap<DispatchPriority, std::pair<int, Watch*>> registeredWatches_;
+ std::multimap<DispatchPriority, Timeout*> registeredTimeouts_;
+
+ std::set<std::pair<DispatchPriority, DispatchSource*>> sourcesToDispatch_;
+ std::set<std::pair<DispatchPriority, std::pair<Watch*, unsigned short>>> watchesToDispatch_;
+ std::set<std::pair<DispatchPriority, Timeout*>> timeoutsToDispatch_;
+
+ DispatchSourceListenerSubscription dispatchSourceListenerSubscription_;
+ WatchListenerSubscription watchListenerSubscription_;
+ TimeoutSourceListenerSubscription timeoutSourceListenerSubscription_;
+ WakeupListenerSubscription wakeupListenerSubscription_;
+
+ int64_t currentMinimalTimeoutInterval_;
+ bool breakLoop_;
+ bool running_;
+
+ pollfd wakeFd_;
+};
+
+
+} // namespace CommonAPI
+
+#endif /* DEMO_MAIN_LOOP_H_ */