test: Add InvocationSpy and ValueSpy helper classes
authorManuel Nickschas <sputnick@quassel-irc.org>
Thu, 4 Oct 2018 16:40:23 +0000 (18:40 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Sun, 18 Nov 2018 10:06:43 +0000 (11:06 +0100)
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
src/test/util/CMakeLists.txt [new file with mode: 0644]
src/test/util/invocationspy.cpp [new file with mode: 0644]
src/test/util/invocationspy.h [new file with mode: 0644]

index ea61c43..02c9b6e 100644 (file)
@@ -1,2 +1,3 @@
 add_subdirectory(global)
 add_subdirectory(main)
 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 (file)
index 0000000..f6c4cc1
--- /dev/null
@@ -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 (file)
index 0000000..74bb3bd
--- /dev/null
@@ -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 (file)
index 0000000..92dd8ec
--- /dev/null
@@ -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 <chrono>
+
+#include <QObject>
+#include <QSignalSpy>
+
+#include <boost/optional.hpp>
+
+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<typename T>
+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<T> _value;
+};
+
+}  // namespace test