diff --git a/src/ipc/ipc-common/CMakeLists.txt b/src/ipc/ipc-common/CMakeLists.txt index 42413c67..c3b3283d 100644 --- a/src/ipc/ipc-common/CMakeLists.txt +++ b/src/ipc/ipc-common/CMakeLists.txt @@ -23,6 +23,7 @@ facelift_add_library(FaceliftIPCCommonLib IPCServiceAdapterBase.h NewIPCServiceAdapterBase.h IPCAttachedPropertyFactory.h + observer.h HEADERS_NO_MOC AppendDBUSSignatureFunction.h ipc-serialization.h diff --git a/src/ipc/ipc-common/IPCProxyBase.h b/src/ipc/ipc-common/IPCProxyBase.h index 25701b8d..6e9e80cc 100644 --- a/src/ipc/ipc-common/IPCProxyBase.h +++ b/src/ipc/ipc-common/IPCProxyBase.h @@ -35,12 +35,6 @@ #include "IPCProxyBinderBase.h" -#if defined(FaceliftIPCCommonLib_LIBRARY) -# define FaceliftIPCCommonLib_EXPORT Q_DECL_EXPORT -#else -# define FaceliftIPCCommonLib_EXPORT Q_DECL_IMPORT -#endif - namespace facelift { template diff --git a/src/ipc/ipc-common/observer.h b/src/ipc/ipc-common/observer.h new file mode 100644 index 00000000..06f87e4e --- /dev/null +++ b/src/ipc/ipc-common/observer.h @@ -0,0 +1,116 @@ +/********************************************************************** +** +** Copyright (C) 2020 Luxoft Sweden AB +** +** This file is part of the FaceLift project +** +** Permission is hereby granted, freIPCServiceAdapterBasee of charge, to any person +** obtaining a copy of this software and associated documentation files +** (the "Software"), to deal in the Software without restriction, +** including without limitation the rights to use, copy, modify, merge, +** publish, distribute, sublicense, and/or sell copies of the Software, +** and to permit persons to whom the Software is furnished to do so, +** subject to the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +** ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. +** +** SPDX-License-Identifier: MIT +** +**********************************************************************/ + +#pragma once + +#include +#include +#include + +namespace facelift { + +class IObserver : public QObject +{ + Q_OBJECT +public: + virtual void IsReadyObserver(const std::shared_ptr &connection) = 0; +}; + +class IsReadyObserver: public QObject +{ + Q_OBJECT + QVector< QPointer > m_observers{}; + +public: + IsReadyObserver() {} + + template + void watch(T const *object, F signal) + { + QObject::connect(object, std::move(signal), this, &IsReadyObserver::readyChanged); + } + + // Set observers + void setObservers(const QVector< QPointer > &observers) { + m_observers.clear(); + for (auto observer: observers) { + Q_ASSERT (observer != nullptr); + m_observers.push_back(observer); + auto connection = std::make_shared(); + *connection = QObject::connect(this, &IsReadyObserver::readyChanged, observer, [observer, connection](){ + observer->IsReadyObserver( connection ); + }); + } + } + + // Get observers + const QVector< QPointer > &getObservers() const { + return m_observers; + } + + Q_SIGNAL void readyChanged(); + + void onReadyChanged() { + emit readyChanged(); + } +}; + +// Single-time observer which will unregister itself when done +template +class SingleTimeObserver : public IObserver +{ + T m_function; + +public: + explicit SingleTimeObserver(T function) : m_function{function} {} + ~SingleTimeObserver() = default; + + void IsReadyObserver(const std::shared_ptr &connection) override { + m_function(); + QObject::disconnect(*connection); + } +}; + +// Standard observer which will work for each signal +template +class StandartObserver : public IObserver +{ + T m_function; + +public: + explicit StandartObserver(T function) : m_function{function} {} + ~StandartObserver() = default; + + void IsReadyObserver(const std::shared_ptr &) override { + m_function(); + } +}; + +} diff --git a/tests/unittest/CMakeLists.txt b/tests/unittest/CMakeLists.txt index 01b98f4a..2bb215c4 100644 --- a/tests/unittest/CMakeLists.txt +++ b/tests/unittest/CMakeLists.txt @@ -6,18 +6,34 @@ if(${GTEST_FOUND}) add_library(GTest::GMock UNKNOWN IMPORTED) set_target_properties(GTest::GMock PROPERTIES IMPORTED_LOCATION ${GMOCK_LIBRARY}) else() - message(WARNING "Google test/mock not found.") + message(ERROR "Google test/mock not found.") endif() + find_package(Qt5 COMPONENTS Test REQUIRED) + if(${QT5TEST_NOTFOUND}) + message(ERROR "Required package Qt5Test not found.") + endif() find_package(Threads REQUIRED) - set(FACELIFT_GTEST_LIBRARIES ${GTEST_BOTH_LIBRARIES} GTest::GMock Threads::Threads) + if(${THREADS_NOTFOUND}) + message(ERROR "Required package Threads not found.") + endif() + set(FACELIFT_GTEST_LIBRARIES ${GTEST_BOTH_LIBRARIES} GTest::GMock Qt5::Test Threads::Threads) include_directories(${GTEST_INCLUDE_DIRS}) facelift_add_test(UnitTests SOURCES FaceliftUtilsTest.cpp - LINK_LIBRARIES ${FACELIFT_GTEST_LIBRARIES} FaceliftCommonLib) + LINK_LIBRARIES + ${FACELIFT_GTEST_LIBRARIES} + FaceliftCommonLib + ) + facelift_add_test(UnitTestsObserver + SOURCES FaceliftObserverTest.cpp + LINK_LIBRARIES + ${FACELIFT_GTEST_LIBRARIES} + FaceliftIPCCommonLib + ) else() - message(WARNING "Required package google test not found!") + message(ERROR "Required package google test not found!") endif() diff --git a/tests/unittest/FaceliftObserverTest.cpp b/tests/unittest/FaceliftObserverTest.cpp new file mode 100644 index 00000000..fea73d8c --- /dev/null +++ b/tests/unittest/FaceliftObserverTest.cpp @@ -0,0 +1,70 @@ +#include +#include "IPCProxyBase.h" +#include "InterfaceBase.h" +#include +#include "observer.h" + +namespace { + +using namespace facelift; + +class Counter +{ +public: + Counter() { m_value = 0; } + + void incValue() { + ++m_value; + } + int getValue() const { + return m_value; + } +private: + int m_value; +}; + +class IPCProxyBaseTest : public ::testing::Test +{ +public: + Counter c1; + Counter c2; + + StandartObserver < std::function > obs1 { std::bind(&Counter::incValue, &c1) }; + SingleTimeObserver< std::function > obs2 { std::bind(&Counter::incValue, &c2) }; + + IPCProxyBase proxyBase{nullptr}; + IsReadyObserver readyObserver{}; + + ~IPCProxyBaseTest() {} +}; + +TEST_F(IPCProxyBaseTest, testObservers) +{ + // Setup an object to watch out for: address and signal + readyObserver.watch( &proxyBase, &IPCProxyBase::readyChanged ); + // Set observers + const auto expected = QVector< QPointer >{ &obs1, &obs2 }; + readyObserver.setObservers(expected); + + // Check values before calling a signal + ASSERT_EQ(c1.getValue(), 0); // for StandartObserver + ASSERT_EQ(c2.getValue(), 0); // for SingleTimeObserver + + // Check handle of signal + QSignalSpy spy(&readyObserver, &IsReadyObserver::readyChanged ); + ASSERT_EQ( spy.isValid(), true); + spy.clear(); + + // Generate signal + ASSERT_EQ(spy.count(), 0); + proxyBase.readyChanged(); + proxyBase.readyChanged(); + proxyBase.readyChanged(); + ASSERT_EQ(spy.count(), 3); + + // Check values after signal call + ASSERT_EQ(c1.getValue(), 3); // for StandartObserver + ASSERT_EQ(c2.getValue(), 1); // for SingleTimeObserver +} + +} // end namespace