From 34e58425ffb5475b210abb484970e674ddb98744 Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Thu, 4 Oct 2018 18:40:23 +0200 Subject: [PATCH] test: Add InvocationSpy and ValueSpy helper classes For test cases testing asynchronous operations that require a running event loop, it is convenient to have a way to wait until something happens. Qt provides QSignalSpy to wait for a specific signal while spinning the event loop. This is not always convenient to use, though. Provide InvocationSpy and ValueSpy (based on QSignalSpy) that provide an explicit way of notifying them, without having to connect a signal first. Unlike QSignalSpy, those classes also time out when not notified within a given time span. Put those classes in a new library Quassel::Test::Util, which will gain more testing-related utils over time. --- src/test/CMakeLists.txt | 1 + src/test/util/CMakeLists.txt | 11 ++++ src/test/util/invocationspy.cpp | 46 ++++++++++++++ src/test/util/invocationspy.h | 103 ++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 src/test/util/CMakeLists.txt create mode 100644 src/test/util/invocationspy.cpp create mode 100644 src/test/util/invocationspy.h diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index ea61c432..02c9b6e6 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(global) add_subdirectory(main) +add_subdirectory(util) diff --git a/src/test/util/CMakeLists.txt b/src/test/util/CMakeLists.txt new file mode 100644 index 00000000..f6c4cc19 --- /dev/null +++ b/src/test/util/CMakeLists.txt @@ -0,0 +1,11 @@ +quassel_add_module(Test::Util EXPORT NOINSTALL) + +target_sources(${TARGET} PRIVATE + invocationspy.cpp +) + +target_link_libraries(${TARGET} + PUBLIC + Quassel::Common + Quassel::Test::Global +) diff --git a/src/test/util/invocationspy.cpp b/src/test/util/invocationspy.cpp new file mode 100644 index 00000000..74bb3bd2 --- /dev/null +++ b/src/test/util/invocationspy.cpp @@ -0,0 +1,46 @@ +/*************************************************************************** + * Copyright (C) 2005-2018 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "invocationspy.h" + +namespace test { + +InvocationSpy::InvocationSpy(QObject* parent) + : QObject(parent) + , _internalSpy{this, &InvocationSpy::notified} +{} + +void InvocationSpy::notify() +{ + emit notified(); +} + +bool InvocationSpy::wait(std::chrono::milliseconds timeout) +{ + if (_internalSpy.count() > 0) { + _internalSpy.clear(); + return true; + } + bool result = _internalSpy.wait(timeout.count()); + _internalSpy.clear(); + return result; +} + +} // namespace test diff --git a/src/test/util/invocationspy.h b/src/test/util/invocationspy.h new file mode 100644 index 00000000..92dd8ec4 --- /dev/null +++ b/src/test/util/invocationspy.h @@ -0,0 +1,103 @@ +/*************************************************************************** + * Copyright (C) 2005-2018 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#pragma once + +#include "test-util-export.h" + +#include + +#include +#include + +#include + +namespace test { + +/** + * Waits while spinning the event loop until notified, or timed out. + * + * Based on QSignalSpy (hence the name), but provides an API that is much more useful + * for writing asynchronous test cases. + */ +class TEST_UTIL_EXPORT InvocationSpy : public QObject +{ + Q_OBJECT + +public: + InvocationSpy(QObject* parent = nullptr); + + /** + * Notifies the spy, which will cause it to return from wait(). + */ + void notify(); + + /** + * Waits for the spy to be notified within the given timeout. + * + * @param timeout Timeout for waiting + * @returns true if the spy was notified, and false if it timed out. + */ + bool wait(std::chrono::milliseconds timeout = std::chrono::seconds{60}); + +signals: + /// Internally used signal + void notified(); + +private: + QSignalSpy _internalSpy; +}; + +/** + * Spy that allows to be notified with a value. + * + * Works like @a InvocationSpy, but takes a value when notified. After successful notification, the value + * can be accessed and used for test case expectations. + */ +template +class ValueSpy : public InvocationSpy +{ +public: + using InvocationSpy::InvocationSpy; + + /** + * Notifies the spy with the given value. + * + * @param value The notification value + */ + void notify(const T& value) + { + _value = value; + InvocationSpy::notify(); + } + + /** + * Provides the value the spy was last notified with. + * + * @note The value is only valid if wait() returned with true. + * @returns The value given to notify(), or boost::none if the spy wasn't notified + */ + T value() const { return *_value; } + +private: + boost::optional _value; +}; + +} // namespace test -- 2.20.1