test: Add a spy type that waits for one or more signals
[quassel.git] / src / test / util / mockedpeer.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "mockedpeer.h"
22
23 #include <boost/any.hpp>
24
25 using namespace ::testing;
26
27 namespace test {
28
29 // ---- Protocol message wrapper -----------------------------------------------------------------------------------------------------------
30
31 struct ProtocolMessage
32 {
33     // Supported protocol message types. If extended, the various visitors in this file need to be extended too.
34     boost::variant<
35         Protocol::SyncMessage,
36         Protocol::RpcCall,
37         Protocol::InitRequest,
38         Protocol::InitData
39     > message;
40 };
41
42 void PrintTo(const ProtocolMessage& msg, std::ostream* os)
43 {
44     struct PrintToVisitor : public boost::static_visitor<void>
45     {
46         PrintToVisitor(std::ostream* os)
47             : _os{os}
48         {}
49
50         void operator()(const Protocol::SyncMessage& syncMessage) const
51         {
52             *_os << "SyncMessage{className = " << PrintToString(syncMessage.className)
53                  << ", objectName = " << PrintToString(syncMessage.objectName)
54                  << ", slotName = " << PrintToString(syncMessage.slotName)
55                  << ", params = " << PrintToString(syncMessage.params)
56                  << "}";
57         }
58
59         void operator()(const Protocol::RpcCall& rpcCall) const
60         {
61             *_os << "RpcCall{slotName = " << PrintToString(rpcCall.slotName)
62                  << ", params = " << PrintToString(rpcCall.params)
63                  << "}";
64         }
65
66         void operator()(const Protocol::InitRequest& initRequest) const
67         {
68             *_os << "InitRequest{className = " << PrintToString(initRequest.className)
69                  << ", objectName = " << PrintToString(initRequest.objectName)
70                  << "}";
71         }
72
73         void operator()(const Protocol::InitData& initData) const
74         {
75             *_os << "InitData{className = " << PrintToString(initData.className)
76                  << ", objectName = " << PrintToString(initData.objectName)
77                  << ", initData = " << PrintToString(initData.initData)
78                  << "}";
79         }
80
81     private:
82         std::ostream* _os;
83     };
84
85     boost::apply_visitor(PrintToVisitor{os}, msg.message);
86 }
87
88 // ---- MockedPeer -------------------------------------------------------------------------------------------------------------------------
89
90 MockedPeer::MockedPeer(QObject* parent)
91     : InternalPeer(parent)
92 {
93     // Default behavior will just delegate to InternalPeer (through the mocked Dispatches() method)
94     ON_CALL(*this, Dispatches(_)).WillByDefault(Invoke(this, &MockedPeer::dispatchInternal));
95 }
96
97 MockedPeer::~MockedPeer() = default;
98
99 void MockedPeer::dispatch(const Protocol::SyncMessage& msg)
100 {
101     Dispatches({msg});
102 }
103
104 void MockedPeer::dispatch(const Protocol::RpcCall& msg)
105 {
106     Dispatches({msg});
107 }
108
109 void MockedPeer::dispatch(const Protocol::InitRequest& msg)
110 {
111     Dispatches({msg});
112 }
113
114 void MockedPeer::dispatch(const Protocol::InitData& msg)
115 {
116     Dispatches({msg});
117 }
118
119 // Unwraps the type before calling the correct overload of realDispatch()
120 struct DispatchVisitor : public boost::static_visitor<void>
121 {
122     DispatchVisitor(MockedPeer* mock)
123         : _mock{mock}
124     {}
125
126     template<typename T>
127     void operator()(const T& message) const
128     {
129         _mock->realDispatch(message);
130     }
131
132     MockedPeer* _mock;
133 };
134
135 void MockedPeer::dispatchInternal(const ProtocolMessage& message)
136 {
137     boost::apply_visitor(DispatchVisitor{this}, message.message);
138 }
139
140 template<typename T>
141 void MockedPeer::realDispatch(const T& message)
142 {
143     InternalPeer::dispatch(message);
144 }
145
146 // ---- Expectations and matchers ----------------------------------------------------------------------------------------------------------
147
148 namespace {
149
150 struct SyncMessageExpectation
151 {
152     Matcher<QByteArray> className;
153     Matcher<QString> objectName;
154     Matcher<QByteArray> slotName;
155     Matcher<QVariantList> params;
156 };
157
158 struct RpcCallExpectation
159 {
160     Matcher<QByteArray> slotName;
161     Matcher<QVariantList> params;
162 };
163
164 struct InitRequestExpectation
165 {
166     Matcher<QByteArray> className;
167     Matcher<QString> objectName;
168 };
169
170 struct InitDataExpectation
171 {
172     Matcher<QByteArray> className;
173     Matcher<QString> objectName;
174     Matcher<QVariantMap> initData;
175 };
176
177 /**
178  * Generic matcher for protocol messages.
179  *
180  * @note The matcher, maybe somewhat surprisingly, always matches; it does, however, add test failures if expectations fail.
181  *       This makes for a much better readable output when used in EXPECT_CALL chains, while still letting test cases fail
182  *       as expected.
183  */
184 class ProtocolMessageMatcher : public MatcherInterface<const ProtocolMessage&>
185 {
186 public:
187     template<typename T>
188     ProtocolMessageMatcher(T expectation)
189         : _expectation{std::move(expectation)}
190     {}
191
192     /**
193      * Visitor used for matching a particular type of protocol message.
194      *
195      * Each supported type requires a corresponding overload for the call operator.
196      */
197     struct MatchVisitor : public boost::static_visitor<bool>
198     {
199         MatchVisitor(const boost::any& expectation)
200             : _expectation{expectation}
201         {}
202
203         bool operator()(const Protocol::SyncMessage& syncMessage) const
204         {
205             auto e = boost::any_cast<SyncMessageExpectation>(&_expectation);
206             if (!e) {
207                 ADD_FAILURE() << "Did not expect a SyncMessage!";
208                 return true;
209             }
210             EXPECT_THAT(syncMessage.className, e->className);
211             EXPECT_THAT(syncMessage.objectName, e->objectName);
212             EXPECT_THAT(syncMessage.slotName, e->slotName);
213             EXPECT_THAT(syncMessage.params, e->params);
214             return true;
215         }
216
217         bool operator()(const Protocol::RpcCall& rpcCall) const
218         {
219             auto e = boost::any_cast<RpcCallExpectation>(&_expectation);
220             if (!e) {
221                 ADD_FAILURE() << "Did not expect an RpcCall!";
222                 return true;
223             }
224             EXPECT_THAT(rpcCall.slotName, e->slotName);
225             EXPECT_THAT(rpcCall.params, e->params);
226             return true;
227         }
228
229         bool operator()(const Protocol::InitRequest& initRequest) const
230         {
231             auto e = boost::any_cast<InitRequestExpectation>(&_expectation);
232             if (!e) {
233                 ADD_FAILURE() << "Did not expect an InitRequest!";
234                 return true;
235             }
236             EXPECT_THAT(initRequest.className, e->className);
237             EXPECT_THAT(initRequest.objectName, e->objectName);
238             return true;
239         }
240
241         bool operator()(const Protocol::InitData& initData) const
242         {
243             auto e = boost::any_cast<InitDataExpectation>(&_expectation);
244             if (!e) {
245                 ADD_FAILURE() << "Did not expect InitData!";
246                 return true;
247             }
248             EXPECT_THAT(initData.className, e->className);
249             EXPECT_THAT(initData.objectName, e->objectName);
250             EXPECT_THAT(initData.initData, e->initData);
251             return true;
252         }
253
254     private:
255         const boost::any& _expectation;
256     };
257
258     bool MatchAndExplain(const ProtocolMessage& protoMsg, MatchResultListener*) const override
259     {
260         return boost::apply_visitor(MatchVisitor{_expectation}, protoMsg.message);
261     }
262
263     void DescribeTo(std::ostream* os) const override
264     {
265         // This should never be actually called because we always match (but fail sub-expectations if appropriate)
266         *os << "Matcher for protocol messages";
267     }
268
269 private:
270     boost::any _expectation;
271 };
272
273 }  // anon
274
275 // Create matcher instances
276
277 Matcher<const ProtocolMessage&> SyncMessage(Matcher<QByteArray> className, Matcher<QString> objectName, Matcher<QByteArray> slotName, Matcher<QVariantList> params)
278 {
279     return MakeMatcher(new ProtocolMessageMatcher{SyncMessageExpectation{std::move(className), std::move(objectName), std::move(slotName), std::move(params)}});
280 }
281
282 Matcher<const ProtocolMessage&> RpcCall(Matcher<QByteArray> slotName, Matcher<QVariantList> params)
283 {
284     return MakeMatcher(new ProtocolMessageMatcher{RpcCallExpectation{std::move(slotName), std::move(params)}});
285 }
286
287 Matcher<const ProtocolMessage&> InitRequest(Matcher<QByteArray> className, Matcher<QString> objectName)
288 {
289     return MakeMatcher(new ProtocolMessageMatcher{InitRequestExpectation{std::move(className), std::move(objectName)}});
290 }
291
292 Matcher<const ProtocolMessage&> InitData(Matcher<QByteArray> className, Matcher<QString> objectName, Matcher<QVariantMap> initData)
293 {
294     return MakeMatcher(new ProtocolMessageMatcher{InitDataExpectation{std::move(className), std::move(objectName), std::move(initData)}});
295 }
296
297 }  // namespace test