tests: Add unit test for basic SignalProxy functionality
[quassel.git] / tests / common / signalproxytest.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 "signalproxy.h"
22
23 #include <utility>
24
25 #include <QByteArray>
26 #include <QTest>
27
28 #include "invocationspy.h"
29 #include "mockedpeer.h"
30 #include "syncableobject.h"
31 #include "testglobal.h"
32
33 using namespace ::testing;
34 using namespace test;
35
36 class SignalProxyTest : public QObject, public ::testing::Test
37 {
38     Q_OBJECT
39
40 public:
41     void SetUp() override
42     {
43         // Set up peers and connect the signal proxies so they're ready to use
44         _clientPeer->setPeer(_serverPeer);
45         _serverPeer->setPeer(_clientPeer);
46         _clientProxy.addPeer(_clientPeer);
47         _serverProxy.addPeer(_serverPeer);
48     }
49
50 protected:
51     SignalProxy _clientProxy{SignalProxy::ProxyMode::Client, this};
52     SignalProxy _serverProxy{SignalProxy::ProxyMode::Server, this};
53     MockedPeer* _clientPeer{new MockedPeer{this}};
54     MockedPeer* _serverPeer{new MockedPeer{this}};
55 };
56
57 // -----------------------------------------------------------------------------------------------------------------------------------------
58
59 // Object for testing attached signals
60 class ProxyObject : public QObject
61 {
62     Q_OBJECT
63
64 public:
65     using Data = std::pair<int, QString>;
66     using Spy = ValueSpy<Data>;
67
68     ProxyObject(Spy* spy)
69         : _spy{spy}
70     {}
71
72 signals:
73     void sendData(int, const QString&);
74     void sendMoreData(int, const QString&);
75
76 public slots:
77     void receiveData(int i, const QString& s) { _spy->notify(std::make_pair(i, s)); }
78     void receiveExtraData(int i, const QString& s) { _spy->notify(std::make_pair(-i, s.toUpper())); }
79
80 private:
81     Spy* _spy;
82 };
83
84 TEST_F(SignalProxyTest, attachSignal)
85 {
86     {
87         InSequence s;
88         EXPECT_CALL(*_clientPeer, Dispatches(RpcCall(Eq(SIGNAL(sendData(int,QString))), ElementsAre(42, "Hello"))));
89         EXPECT_CALL(*_clientPeer, Dispatches(RpcCall(Eq(SIGNAL(sendExtraData(int,QString))), ElementsAre(42, "Hello"))));
90         EXPECT_CALL(*_serverPeer, Dispatches(RpcCall(Eq("2sendData(int,QString)"), ElementsAre(23, "World"))));
91     }
92
93     ProxyObject::Spy clientSpy, serverSpy;
94     ProxyObject clientObject{&clientSpy};
95     ProxyObject serverObject{&serverSpy};
96
97     // Deliberately not normalize some of the macro invocations
98     _clientProxy.attachSignal(&clientObject, SIGNAL(sendData(int, const QString&)));
99     _serverProxy.attachSlot(SIGNAL(sendData(int,QString)), &serverObject, SLOT(receiveData(int, const QString&)));
100
101     _clientProxy.attachSignal(&clientObject, SIGNAL(sendMoreData(int,QString)), SIGNAL(sendExtraData(int,QString)));
102     _serverProxy.attachSlot(SIGNAL(sendExtraData(int, const QString&)), &serverObject, SLOT(receiveExtraData(int,QString)));
103
104     _serverProxy.attachSignal(&serverObject, SIGNAL(sendData(int,QString)));
105     _clientProxy.attachSlot(SIGNAL(sendData(int, const QString&)), &clientObject, SLOT(receiveData(int,QString)));
106
107     emit clientObject.sendData(42, "Hello");
108     ASSERT_TRUE(serverSpy.wait());
109     EXPECT_EQ(ProxyObject::Data(42, "Hello"), serverSpy.value());
110
111     emit clientObject.sendMoreData(42, "Hello");
112     ASSERT_TRUE(serverSpy.wait());
113     EXPECT_EQ(ProxyObject::Data(-42, "HELLO"), serverSpy.value());
114
115     emit serverObject.sendData(23, "World");
116     ASSERT_TRUE(clientSpy.wait());
117     EXPECT_EQ(ProxyObject::Data(23, "World"), clientSpy.value());
118 }
119
120 // -----------------------------------------------------------------------------------------------------------------------------------------
121
122 class SyncObj : public SyncableObject
123 {
124     Q_OBJECT
125     SYNCABLE_OBJECT
126
127     Q_PROPERTY(int intProperty READ intProperty WRITE setIntProperty NOTIFY intPropertyChanged)
128     Q_PROPERTY(QString stringProperty READ stringProperty WRITE setStringProperty NOTIFY stringPropertyChanged)
129     Q_PROPERTY(double doubleProperty READ doubleProperty WRITE setDoubleProperty)
130
131 public:
132
133     int intProperty() const
134     {
135         return _intProperty;
136     }
137
138     QString stringProperty() const
139     {
140         return _stringProperty;
141     }
142
143     double doubleProperty() const
144     {
145         return _doubleProperty;
146     }
147
148     int syncedInt() const
149     {
150         return _syncedInt;
151     }
152
153     QString syncedString() const
154     {
155         return _syncedString;
156     }
157
158 public slots:
159     QByteArray initFooData() const
160     {
161         return _fooData;
162     }
163
164     void setInitFooData(const QByteArray& data)
165     {
166         _fooData = data;
167         emit fooDataChanged(data);
168     }
169
170     void setIntProperty(int value)
171     {
172         _intProperty = value;
173         SYNC(ARG(value));
174         emit intPropertyChanged(value);
175     }
176
177     void setStringProperty(const QString& value)
178     {
179         _stringProperty = value;
180         SYNC(ARG(value));
181         emit stringPropertyChanged(value);
182     }
183
184     // Deliberately no sync nor changed signal
185     void setDoubleProperty(double value)
186     {
187         _doubleProperty = value;
188     }
189
190     void syncMethod(int intArg, const QString& stringArg)
191     {
192         _syncedInt = intArg;
193         _syncedString = stringArg;
194         SYNC(ARG(intArg), ARG(stringArg));
195         SYNC_OTHER(setIntProperty, ARG(intArg));
196         SYNC_OTHER(setStringProperty, ARG(stringArg));
197         emit syncMethodCalled(intArg, stringArg);
198     }
199
200     void requestMethod(const QString& stringArg, int intArg)
201     {
202         REQUEST(ARG(stringArg), ARG(intArg));
203         REQUEST_OTHER(setStringProperty, ARG(stringArg));
204         emit requestMethodCalled(stringArg, intArg);
205     }
206
207 signals:
208     void intPropertyChanged(int);
209     void stringPropertyChanged(const QString&);
210     void fooDataChanged(const QByteArray&);
211     void syncMethodCalled(int, const QString&);
212     void requestMethodCalled(const QString&, int);
213
214 private:
215     int _intProperty{};
216     QString _stringProperty;
217     double _doubleProperty{};
218     QByteArray _fooData{"FOO"};
219     int _syncedInt{};
220     QString _syncedString;
221 };
222
223 TEST_F(SignalProxyTest, syncableObject)
224 {
225     {
226         InSequence s;
227
228         // Synchronize
229         EXPECT_CALL(*_clientPeer, Dispatches(InitRequest(Eq("SyncObj"), Eq("Foo"))));
230         EXPECT_CALL(*_serverPeer, Dispatches(InitData(Eq("SyncObj"), Eq("Foo"), QVariantMap{
231                                                           {"stringProperty", "Hello"},
232                                                           {"intProperty", 42},
233                                                           {"doubleProperty", 4.2},
234                                                           {"FooData", "FOO"}
235                                                       })));
236
237         // Set int property
238         EXPECT_CALL(*_serverPeer, Dispatches(SyncMessage(Eq("SyncObj"), Eq("Foo"), Eq("setIntProperty"), ElementsAre(23))));
239
240         // Sync method
241         EXPECT_CALL(*_serverPeer, Dispatches(SyncMessage(Eq("SyncObj"), Eq("Foo"), Eq("syncMethod"), ElementsAre(42, "World"))));
242         EXPECT_CALL(*_serverPeer, Dispatches(SyncMessage(Eq("SyncObj"), Eq("Foo"), Eq("setIntProperty"), ElementsAre(42))));
243         EXPECT_CALL(*_serverPeer, Dispatches(SyncMessage(Eq("SyncObj"), Eq("Foo"), Eq("setStringProperty"), ElementsAre("World"))));
244
245         // Request method
246         EXPECT_CALL(*_clientPeer, Dispatches(SyncMessage(Eq("SyncObj"), Eq("Foo"), Eq("requestMethod"), ElementsAre("Hello", 23))));
247         EXPECT_CALL(*_clientPeer, Dispatches(SyncMessage(Eq("SyncObj"), Eq("Foo"), Eq("setStringProperty"), ElementsAre("Hello"))));
248
249         // Update properties (twice)
250         EXPECT_CALL(*_clientPeer, Dispatches(SyncMessage(Eq("SyncObj"), Eq("Foo"), Eq("requestUpdate"), ElementsAre(QVariantMap{
251                                                              {"stringProperty", "Quassel"},
252                                                              {"intProperty", 17},
253                                                              {"doubleProperty", 2.3}
254                                                          })))).Times(2);
255         EXPECT_CALL(*_serverPeer, Dispatches(SyncMessage(Eq("SyncObj"), Eq("Foo"), Eq("setIntProperty"), ElementsAre(17))));
256         EXPECT_CALL(*_serverPeer, Dispatches(SyncMessage(Eq("SyncObj"), Eq("Foo"), Eq("setStringProperty"), ElementsAre("Quassel"))));
257         EXPECT_CALL(*_serverPeer, Dispatches(SyncMessage(Eq("SyncObj"), Eq("Foo"), Eq("update"), ElementsAre(QVariantMap{
258                                                              {"stringProperty", "Quassel"},
259                                                              {"intProperty", 17},
260                                                              {"doubleProperty", 2.3}
261                                                          }))));
262     }
263
264     SignalSpy spy;
265
266     SyncObj clientObject;
267     SyncObj serverObject;
268
269     // -- Set initial data
270
271     serverObject.setIntProperty(42);
272     serverObject.setStringProperty("Hello");
273     serverObject.setDoubleProperty(4.2);
274     serverObject.setObjectName("Foo");
275     clientObject.setObjectName("Foo");
276
277     // -- Synchronize
278
279     spy.connect(&serverObject, &SyncableObject::initDone);
280     _serverProxy.synchronize(&serverObject);
281     ASSERT_TRUE(spy.wait());
282     spy.connect(&clientObject, &SyncableObject::initDone);
283     _clientProxy.synchronize(&clientObject);
284     ASSERT_TRUE(spy.wait());
285
286     // -- Check if client-side values are as expected
287
288     EXPECT_EQ(42, clientObject.intProperty());
289     EXPECT_EQ("Hello", clientObject.stringProperty());
290     EXPECT_EQ(4.2, clientObject.doubleProperty());
291     EXPECT_EQ("FOO", clientObject.initFooData());
292
293     // -- Set int property
294     spy.connect(&clientObject, &SyncObj::intPropertyChanged);
295     serverObject.setIntProperty(23);
296     ASSERT_TRUE(spy.wait());
297     EXPECT_EQ(23, clientObject.intProperty());
298
299     // -- Sync method
300
301     spy.connect(&clientObject, &SyncObj::syncMethodCalled);
302     serverObject.syncMethod(42, "World");
303     ASSERT_TRUE(spy.wait());
304     EXPECT_EQ(42, clientObject.syncedInt());
305     EXPECT_EQ(42, clientObject.intProperty());
306     EXPECT_EQ("World", clientObject.syncedString());
307     EXPECT_EQ("World", clientObject.stringProperty());
308
309     // -- Request method
310
311     spy.connect(&serverObject, &SyncObj::requestMethodCalled);
312     clientObject.requestMethod("Hello", 23);
313     ASSERT_TRUE(spy.wait());
314     EXPECT_EQ("Hello", serverObject.stringProperty());
315
316     // -- Property update
317
318     QVariantMap propMap{{"intProperty", 17}, {"stringProperty", "Quassel"}, {"doubleProperty", 2.3}};
319     spy.connect(&serverObject, &SyncableObject::updatedRemotely);
320     clientObject.requestUpdate(propMap);
321     ASSERT_TRUE(spy.wait());
322     // We don't allow client updates yet, so the values shouldn't have changed
323     EXPECT_EQ(23, serverObject.intProperty());
324     EXPECT_EQ("Hello", serverObject.stringProperty());
325     EXPECT_EQ(4.2, serverObject.doubleProperty());
326     // Allow client updates, try again
327     serverObject.setAllowClientUpdates(true);
328     spy.connect(&clientObject, &SyncableObject::updated);
329     clientObject.requestUpdate(propMap);
330     ASSERT_TRUE(spy.wait());
331     EXPECT_EQ(17, serverObject.intProperty());
332     EXPECT_EQ("Quassel", serverObject.stringProperty());
333     EXPECT_EQ(2.3, serverObject.doubleProperty());
334     EXPECT_EQ(17, clientObject.intProperty());
335     EXPECT_EQ("Quassel", clientObject.stringProperty());
336     EXPECT_EQ(2.3, clientObject.doubleProperty());
337 }
338
339 #include "signalproxytest.moc"