diff options
author | Johannes Schanda <schanda@itestra.de> | 2013-05-06 12:09:06 +0200 |
---|---|---|
committer | Gerrit Code Review <qqmthk1@lpmodthk02.bmwgroup.net> | 2013-05-06 12:09:06 +0200 |
commit | c3996fe7181c6b16154fe8cc8fb441a911154794 (patch) | |
tree | 115e39e31ae5ec5a96f992e8cd32feaed0a96166 | |
parent | 8867ca4b3833f89bb1fae69f92d3a2cdac5d50b9 (diff) | |
parent | 0b137b5e253265f3033b0b4fa99b74e340274233 (diff) | |
download | genivi-common-api-dbus-runtime-c3996fe7181c6b16154fe8cc8fb441a911154794.tar.gz |
Merge "Added Unit Tests and Demo Implementation for Mainloops"
-rw-r--r-- | Makefile.am | 10 | ||||
-rw-r--r-- | configure.ac | 28 | ||||
-rw-r--r-- | src/CommonAPI/DBus/DBusConnection.h | 5 | ||||
-rw-r--r-- | src/test/DBusMainLoopIntegrationTest.cpp | 545 | ||||
-rw-r--r-- | src/test/DemoMainLoop.h | 334 |
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_ */ |