1 #include <tests/lib/test.h>
2
3 #include <tests/lib/glib-helpers/test-conn-helper.h>
4
5 #include <tests/lib/glib/contacts-conn.h>
6
7 #include <TelepathyQt/Account>
8 #include <TelepathyQt/ChannelFactory>
9 #include <TelepathyQt/ConnectionFactory>
10 #include <TelepathyQt/PendingComposite>
11 #include <TelepathyQt/PendingReady>
12
13 #include <telepathy-glib/debug.h>
14
15 using namespace Tp;
16 using namespace Tp::Client;
17
18 // A really minimal Account implementation, totally not spec compliant outside the parts stressed by
19 // this test
20 class AccountAdaptor : public QDBusAbstractAdaptor
21 {
22 Q_OBJECT
23 Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Account")
24 Q_CLASSINFO("D-Bus Introspection", ""
25 " <interface name=\"org.freedesktop.Telepathy.Account\" >\n"
26 " <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
27 " <property name=\"Connection\" type=\"o\" access=\"read\" />\n"
28 " <signal name=\"AccountPropertyChanged\" >\n"
29 " <arg name=\"Properties\" type=\"a{sv}\" />\n"
30 " </signal>\n"
31 " </interface>\n"
32 "")
33
34 Q_PROPERTY(QDBusObjectPath Connection READ Connection)
35 Q_PROPERTY(QStringList Interfaces READ Interfaces)
36
37 public:
AccountAdaptor(QObject * parent)38 AccountAdaptor(QObject *parent)
39 : QDBusAbstractAdaptor(parent), mConnection(QLatin1String("/"))
40 {
41 }
42
~AccountAdaptor()43 virtual ~AccountAdaptor()
44 {
45 }
46
setConnection(QString conn)47 void setConnection(QString conn)
48 {
49 if (conn.isEmpty()) {
50 conn = QLatin1String("/");
51 }
52
53 mConnection = QDBusObjectPath(conn);
54 QVariantMap props;
55 props.insert(QLatin1String("Connection"), QVariant::fromValue(mConnection));
56 Q_EMIT AccountPropertyChanged(props);
57 }
58
59 public: // Properties
Connection() const60 inline QDBusObjectPath Connection() const
61 {
62 return mConnection;
63 }
64
Interfaces() const65 inline QStringList Interfaces() const
66 {
67 return QStringList();
68 }
69
70 Q_SIGNALS: // Signals
71 void AccountPropertyChanged(const QVariantMap &properties);
72
73 private:
74 QDBusObjectPath mConnection;
75 };
76
77 class TestAccountConnectionFactory : public Test
78 {
79 Q_OBJECT
80
81 public:
TestAccountConnectionFactory(QObject * parent=0)82 TestAccountConnectionFactory(QObject *parent = 0)
83 : Test(parent),
84 mConn1(0), mConn2(0),
85 mDispatcher(0), mAccountAdaptor(0),
86 mReceivedHaveConnection(0), mReceivedConn(0)
87 { }
88
89 protected Q_SLOTS:
90 void onConnectionChanged(const Tp::ConnectionPtr &conn);
91 void expectPropertyChange(const QString &property);
92
93 private Q_SLOTS:
94 void initTestCase();
95 void init();
96
97 void testIntrospectSeveralAccounts();
98 void testCreateAndIntrospect();
99 void testDefaultFactoryInitialConn();
100 void testReadifyingFactoryInitialConn();
101 void testSwitch();
102 void testQueuedSwitch();
103
104 void cleanup();
105 void cleanupTestCase();
106
107 private:
108 TestConnHelper *mConn1, *mConn2;
109 QObject *mDispatcher;
110 QString mAccountBusName, mAccountPath;
111 AccountAdaptor *mAccountAdaptor;
112 AccountPtr mAccount;
113 bool *mReceivedHaveConnection;
114 QString *mReceivedConn;
115 QStringList mReceivedConns;
116 };
117
onConnectionChanged(const Tp::ConnectionPtr & conn)118 void TestAccountConnectionFactory::onConnectionChanged(const Tp::ConnectionPtr &conn)
119 {
120 qDebug() << "have connection:" << !conn.isNull();
121
122 if (mReceivedHaveConnection) {
123 delete mReceivedHaveConnection;
124 }
125
126 mReceivedHaveConnection = new bool(!conn.isNull());
127 }
128
expectPropertyChange(const QString & property)129 void TestAccountConnectionFactory::expectPropertyChange(const QString &property)
130 {
131 if (property != QLatin1String("connection")) {
132 // Not interesting
133 return;
134 }
135
136 ConnectionPtr conn = mAccount->connection();
137 qDebug() << "connection changed:" << (conn ? conn->objectPath() : QLatin1String("none"));
138
139 if (conn) {
140 QCOMPARE(conn->objectPath(), conn->objectPath());
141 }
142
143 if (mReceivedConn) {
144 delete mReceivedConn;
145 }
146
147 mReceivedConn = new QString(conn ? conn->objectPath() : QLatin1String(""));
148 mReceivedConns.push_back(conn ? conn->objectPath() : QLatin1String(""));
149 }
150
initTestCase()151 void TestAccountConnectionFactory::initTestCase()
152 {
153 initTestCaseImpl();
154
155 g_type_init();
156 g_set_prgname("account-connection-factory");
157 tp_debug_set_flags("all");
158 dbus_g_bus_get(DBUS_BUS_STARTER, 0);
159
160 mConn1 = new TestConnHelper(this,
161 TP_TESTS_TYPE_CONTACTS_CONNECTION,
162 "account", "me@example.com",
163 "protocol", "simple",
164 NULL);
165 QCOMPARE(mConn1->isReady(), false);
166
167 mConn2 = new TestConnHelper(this,
168 TP_TESTS_TYPE_CONTACTS_CONNECTION,
169 "account", "me2@example.com",
170 "protocol", "simple",
171 NULL);
172 QCOMPARE(mConn2->isReady(), false);
173
174 mAccountBusName = TP_QT_IFACE_ACCOUNT_MANAGER;
175 mAccountPath = QLatin1String("/org/freedesktop/Telepathy/Account/simple/simple/account");
176 }
177
init()178 void TestAccountConnectionFactory::init()
179 {
180 initImpl();
181
182 mDispatcher = new QObject(this);
183
184 mAccountAdaptor = new AccountAdaptor(mDispatcher);
185
186 QDBusConnection bus = QDBusConnection::sessionBus();
187 QVERIFY(bus.registerService(mAccountBusName));
188 QVERIFY(bus.registerObject(mAccountPath, mDispatcher));
189 }
190
191 // If this test fails, probably the code which tries to introspect the CD just once and then
192 // continue with Account introspection has a bug
testIntrospectSeveralAccounts()193 void TestAccountConnectionFactory::testIntrospectSeveralAccounts()
194 {
195 QList<PendingOperation *> ops;
196 for (int i = 0; i < 10; i++) {
197 AccountPtr acc = Account::create(mAccountBusName, mAccountPath);
198
199 // This'll get the CD introspected in the middle (but won't finish any of the pending ops,
200 // as they'll only finish in a singleShot in the next iter)
201 //
202 // One iteration to get readinessHelper to start introspecting,
203 // the second to download the CD property
204 // the third to get PendingVariant to actually emit the finished signal for it
205 if (i == 5) {
206 mLoop->processEvents();
207 mLoop->processEvents();
208 mLoop->processEvents();
209 }
210
211 ops.push_back(acc->becomeReady());
212 }
213
214 QVERIFY(connect(new PendingComposite(ops, SharedPtr<RefCounted>()),
215 SIGNAL(finished(Tp::PendingOperation*)),
216 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
217 QCOMPARE(mLoop->exec(), 0);
218 }
219
220 // If this test fails, probably the mini-Account implements too little for the Account proxy to work
221 // OR the Account proxy is completely broken :)
testCreateAndIntrospect()222 void TestAccountConnectionFactory::testCreateAndIntrospect()
223 {
224 mAccount = Account::create(mAccountBusName, mAccountPath);
225
226 QVERIFY(connect(mAccount->becomeReady(),
227 SIGNAL(finished(Tp::PendingOperation*)),
228 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
229 QCOMPARE(mLoop->exec(), 0);
230 }
231
testDefaultFactoryInitialConn()232 void TestAccountConnectionFactory::testDefaultFactoryInitialConn()
233 {
234 mAccountAdaptor->setConnection(mConn1->objectPath());
235
236 mAccount = Account::create(mAccountBusName, mAccountPath);
237
238 QVERIFY(connect(mAccount->becomeReady(),
239 SIGNAL(finished(Tp::PendingOperation*)),
240 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
241 QCOMPARE(mLoop->exec(), 0);
242
243 QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
244 QVERIFY(!mAccount->connection().isNull());
245
246 QCOMPARE(mAccount->connectionFactory()->features(), Features());
247 }
248
testReadifyingFactoryInitialConn()249 void TestAccountConnectionFactory::testReadifyingFactoryInitialConn()
250 {
251 mAccountAdaptor->setConnection(mConn1->objectPath());
252
253 mAccount = Account::create(mAccountBusName, mAccountPath,
254 ConnectionFactory::create(QDBusConnection::sessionBus(),
255 Connection::FeatureCore),
256 ChannelFactory::create(QDBusConnection::sessionBus()));
257
258 QVERIFY(connect(mAccount->becomeReady(),
259 SIGNAL(finished(Tp::PendingOperation*)),
260 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
261 QCOMPARE(mLoop->exec(), 0);
262
263 QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
264
265 ConnectionPtr conn = mAccount->connection();
266 QVERIFY(!conn.isNull());
267
268 QVERIFY(conn->isReady(Connection::FeatureCore));
269
270 QCOMPARE(mAccount->connectionFactory()->features(), Features(Connection::FeatureCore));
271 }
272
testSwitch()273 void TestAccountConnectionFactory::testSwitch()
274 {
275 mAccount = Account::create(mAccountBusName, mAccountPath,
276 ConnectionFactory::create(QDBusConnection::sessionBus(),
277 Connection::FeatureCore),
278 ChannelFactory::create(QDBusConnection::sessionBus()));
279
280 QVERIFY(connect(mAccount->becomeReady(),
281 SIGNAL(finished(Tp::PendingOperation*)),
282 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
283 QCOMPARE(mLoop->exec(), 0);
284
285 QVERIFY(mAccount->connection().isNull());
286
287 QVERIFY(connect(mAccount.data(),
288 SIGNAL(connectionChanged(const Tp::ConnectionPtr &)),
289 SLOT(onConnectionChanged(const Tp::ConnectionPtr &))));
290
291 QVERIFY(connect(mAccount.data(),
292 SIGNAL(propertyChanged(QString)),
293 SLOT(expectPropertyChange(QString))));
294
295 // Switch from none to conn 1
296 mAccountAdaptor->setConnection(mConn1->objectPath());
297 while (!mReceivedHaveConnection || !mReceivedConn) {
298 mLoop->processEvents();
299 }
300 QCOMPARE(*mReceivedHaveConnection, true);
301 QCOMPARE(*mReceivedConn, mConn1->objectPath());
302
303 delete mReceivedHaveConnection;
304 mReceivedHaveConnection = 0;
305
306 delete mReceivedConn;
307 mReceivedConn = 0;
308
309 QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
310 QVERIFY(!mAccount->connection().isNull());
311
312 ConnectionPtr conn = mAccount->connection();
313 QVERIFY(!conn.isNull());
314 QCOMPARE(conn->objectPath(), mConn1->objectPath());
315
316 QVERIFY(conn->isReady(Connection::FeatureCore));
317
318 // Switch from conn 1 to conn 2
319 mAccountAdaptor->setConnection(mConn2->objectPath());
320 while (!mReceivedConn) {
321 mLoop->processEvents();
322 }
323 QCOMPARE(*mReceivedConn, mConn2->objectPath());
324
325 delete mReceivedConn;
326 mReceivedConn = 0;
327
328 // connectionChanged() should have been emitted as it is a new connection
329 QVERIFY(mReceivedHaveConnection);
330 QVERIFY(!mAccount->connection().isNull());
331
332 QCOMPARE(mAccount->connection()->objectPath(), mConn2->objectPath());
333
334 conn = mAccount->connection();
335 QVERIFY(!conn.isNull());
336 QCOMPARE(conn->objectPath(), mConn2->objectPath());
337
338 QVERIFY(conn->isReady(Connection::FeatureCore));
339
340 // Switch from conn 2 to none
341 mAccountAdaptor->setConnection(QString());
342 while (!mReceivedHaveConnection || !mReceivedConn) {
343 mLoop->processEvents();
344 }
345 QCOMPARE(*mReceivedConn, QString());
346 QCOMPARE(*mReceivedHaveConnection, false);
347
348 QVERIFY(mAccount->connection().isNull());
349 }
350
testQueuedSwitch()351 void TestAccountConnectionFactory::testQueuedSwitch()
352 {
353 mAccount = Account::create(mAccountBusName, mAccountPath,
354 ConnectionFactory::create(QDBusConnection::sessionBus(),
355 Connection::FeatureCore),
356 ChannelFactory::create(QDBusConnection::sessionBus()));
357
358 QVERIFY(connect(mAccount->becomeReady(),
359 SIGNAL(finished(Tp::PendingOperation*)),
360 SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
361 QCOMPARE(mLoop->exec(), 0);
362
363 QVERIFY(mAccount->connection().isNull());
364
365 QVERIFY(connect(mAccount.data(),
366 SIGNAL(propertyChanged(QString)),
367 SLOT(expectPropertyChange(QString))));
368
369 // Switch a few times but don't give the proxy update machinery time to run
370 mAccountAdaptor->setConnection(mConn1->objectPath());
371 mAccountAdaptor->setConnection(QString());
372 mAccountAdaptor->setConnection(mConn2->objectPath());
373 mAccountAdaptor->setConnection(QString());
374 mAccountAdaptor->setConnection(QString());
375 mAccountAdaptor->setConnection(QString());
376 mAccountAdaptor->setConnection(mConn2->objectPath());
377 mAccountAdaptor->setConnection(QString());
378 mAccountAdaptor->setConnection(mConn2->objectPath());
379 mAccountAdaptor->setConnection(mConn2->objectPath());
380 mAccountAdaptor->setConnection(mConn1->objectPath());
381
382 // We should get a total of 8 changes because some of them aren't actually any different
383 while (mReceivedConns.size() < 8) {
384 mLoop->processEvents();
385 }
386 // To ensure it didn't go over, which might be possible if it handled two events in one iter
387 QCOMPARE(mReceivedConns.size(), 8);
388
389 // Ensure we got them in the correct order
390 QCOMPARE(mReceivedConns, QStringList() << mConn1->objectPath()
391 << QString()
392 << mConn2->objectPath()
393 << QString()
394 << mConn2->objectPath()
395 << QString()
396 << mConn2->objectPath()
397 << mConn1->objectPath());
398
399 // Check that the final state is correct
400 QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
401 QVERIFY(!mAccount->connection().isNull());
402 }
403
cleanup()404 void TestAccountConnectionFactory::cleanup()
405 {
406 mAccount.reset();
407
408 if (mReceivedHaveConnection) {
409 delete mReceivedHaveConnection;
410 mReceivedHaveConnection = 0;
411 }
412
413 if (mReceivedConn) {
414 delete mReceivedConn;
415 mReceivedConn = 0;
416 }
417
418 if (mAccountAdaptor) {
419 delete mAccountAdaptor;
420 mAccountAdaptor = 0;
421 }
422
423 if (mDispatcher) {
424 delete mDispatcher;
425 mDispatcher = 0;
426 }
427
428 mReceivedConns.clear();
429
430 cleanupImpl();
431 }
432
cleanupTestCase()433 void TestAccountConnectionFactory::cleanupTestCase()
434 {
435 if (mConn1) {
436 QCOMPARE(mConn1->disconnect(), true);
437 delete mConn1;
438 }
439
440 if (mConn2) {
441 QCOMPARE(mConn2->disconnect(), true);
442 delete mConn2;
443 }
444
445 cleanupTestCaseImpl();
446 }
447
448 QTEST_MAIN(TestAccountConnectionFactory)
449 #include "_gen/account-connection-factory.cpp.moc.hpp"
450