--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/auto/qcontactasync/unittest/tst_qcontactasync.cpp Wed Aug 25 15:49:42 2010 +0300
@@ -0,0 +1,2358 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Mobility Components.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+
+#include <QCoreApplication>
+#include <QScopedPointer>
+
+#include "qtcontacts.h"
+#include "qcontactmanagerdataholder.h" //QContactManagerDataHolder
+
+QTM_USE_NAMESPACE
+/* Define an innocuous request (fetch ie doesn't mutate) to "fill up" any queues */
+#define FILL_QUEUE_WITH_FETCH_REQUESTS() QContactFetchRequest fqcfr1, fqcfr2, fqcfr3; \
+ QContactDetailDefinitionFetchRequest fqdfr1, fqdfr2, fqdfr3; \
+ fqcfr1.start(); \
+ fqcfr2.start(); \
+ fqcfr3.start(); \
+ fqdfr1.start(); \
+ fqdfr2.start(); \
+ fqdfr3.start();
+
+
+//TESTED_CLASS=
+//TESTED_FILES=
+
+// Unfortunately the plumbing isn't in place to allow cancelling requests at arbitrary points
+// in their processing. So we do multiple loops until things work out.. or not
+#define MAX_OPTIMISTIC_SCHEDULING_LIMIT 100
+
+
+// Thread capable QThreadSignalSpy (to avoid data races with count/appendArgS)
+class QThreadSignalSpy: public QObject
+{
+public:
+ QThreadSignalSpy(QObject *obj, const char *aSignal)
+ {
+ QMutexLocker m(&lock);
+#ifdef Q_CC_BOR
+ const int memberOffset = QObject::staticMetaObject.methodCount();
+#else
+ static const int memberOffset = QObject::staticMetaObject.methodCount();
+#endif
+ Q_ASSERT(obj);
+ Q_ASSERT(aSignal);
+
+ if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) {
+ qWarning("QThreadSignalSpy: Not a valid signal, use the SIGNAL macro");
+ return;
+ }
+
+ QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1);
+ const QMetaObject *mo = obj->metaObject();
+ int sigIndex = mo->indexOfMethod(ba.constData());
+ if (sigIndex < 0) {
+ qWarning("QThreadSignalSpy: No such signal: '%s'", ba.constData());
+ return;
+ }
+
+ if (!QMetaObject::connect(obj, sigIndex, this, memberOffset,
+ Qt::DirectConnection, 0)) {
+ qWarning("QThreadSignalSpy: QMetaObject::connect returned false. Unable to connect.");
+ return;
+ }
+ sig = ba;
+ initArgs(mo->method(sigIndex));
+ }
+
+ inline bool isValid() const { return !sig.isEmpty(); }
+ inline QByteArray signal() const { return sig; }
+
+ int qt_metacall(QMetaObject::Call call, int methodId, void **a)
+ {
+ methodId = QObject::qt_metacall(call, methodId, a);
+ if (methodId < 0)
+ return methodId;
+
+ if (call == QMetaObject::InvokeMetaMethod) {
+ if (methodId == 0) {
+ appendArgs(a);
+ }
+ --methodId;
+ }
+ return methodId;
+ }
+
+ // The QList<QVariantList> API we actually use
+ int count() const
+ {
+ QMutexLocker m(&lock);
+ return savedArgs.count();
+ }
+ void clear()
+ {
+ QMutexLocker m(&lock);
+ savedArgs.clear();
+ }
+
+private:
+ void initArgs(const QMetaMethod &member)
+ {
+ QList<QByteArray> params = member.parameterTypes();
+ for (int i = 0; i < params.count(); ++i) {
+ int tp = QMetaType::type(params.at(i).constData());
+ if (tp == QMetaType::Void)
+ qWarning("Don't know how to handle '%s', use qRegisterMetaType to register it.",
+ params.at(i).constData());
+ args << tp;
+ }
+ }
+
+ void appendArgs(void **a)
+ {
+ QMutexLocker m(&lock);
+ QList<QVariant> list;
+ for (int i = 0; i < args.count(); ++i) {
+ QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
+ list << QVariant(type, a[i + 1]);
+ }
+ savedArgs.append(list);
+ }
+
+ // the full, normalized signal name
+ QByteArray sig;
+ // holds the QMetaType types for the argument list of the signal
+ QList<int> args;
+
+ mutable QMutex lock;
+ // Different API
+ QList< QVariantList> savedArgs;
+};
+
+class tst_QContactAsync : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QContactAsync();
+ virtual ~tst_QContactAsync();
+
+public slots:
+ void initTestCase();
+ void cleanupTestCase();
+
+private:
+ void addManagers(QStringList includes = QStringList()); // add standard managers to the data
+
+private slots:
+ void testDestructor();
+ void testDestructor_data() { addManagers(QStringList(QString("maliciousplugin"))); }
+
+ void contactFetch();
+ void contactFetch_data() { addManagers(); }
+ void contactIdFetch();
+ void contactIdFetch_data() { addManagers(); }
+ void contactRemove();
+ void contactRemove_data() { addManagers(); }
+ void contactSave();
+ void contactSave_data() { addManagers(); }
+
+ void definitionFetch();
+ void definitionFetch_data() { addManagers(); }
+ void definitionRemove();
+ void definitionRemove_data() { addManagers(); }
+ void definitionSave();
+ void definitionSave_data() { addManagers(); }
+
+ void relationshipFetch();
+ void relationshipFetch_data() { addManagers(); }
+ void relationshipRemove();
+ void relationshipRemove_data() { addManagers(); }
+ void relationshipSave();
+ void relationshipSave_data() { addManagers(); }
+
+ void maliciousManager(); // uses it's own custom data (manager)
+
+ void testQuickDestruction();
+ void testQuickDestruction_data() { addManagers(QStringList(QString("maliciousplugin"))); }
+
+ void threadDelivery();
+ void threadDelivery_data() { addManagers(QStringList(QString("maliciousplugin"))); }
+protected slots:
+ void resultsAvailableReceived();
+ void deleteRequest();
+
+private:
+ bool compareContactLists(QList<QContact> lista, QList<QContact> listb);
+ bool compareContacts(QContact ca, QContact cb);
+ bool containsIgnoringTimestamps(const QList<QContact>& list, const QContact& c);
+ bool compareIgnoringTimestamps(const QContact& ca, const QContact& cb);
+ QContactManager* prepareModel(const QString& uri);
+
+ Qt::HANDLE m_mainThreadId;
+ Qt::HANDLE m_resultsAvailableSlotThreadId;
+ QScopedPointer<QContactManagerDataHolder> managerDataHolder;
+};
+
+tst_QContactAsync::tst_QContactAsync()
+{
+ // ensure we can load all of the plugins we need to.
+ QString path = QApplication::applicationDirPath() + "/dummyplugin/plugins";
+ QApplication::addLibraryPath(path);
+
+ qRegisterMetaType<QContactAbstractRequest::State>("QContactAbstractRequest::State");
+}
+
+tst_QContactAsync::~tst_QContactAsync()
+{
+}
+
+void tst_QContactAsync::initTestCase()
+{
+ managerDataHolder.reset(new QContactManagerDataHolder());
+}
+
+void tst_QContactAsync::cleanupTestCase()
+{
+ managerDataHolder.reset(0);
+}
+
+bool tst_QContactAsync::compareContactLists(QList<QContact> lista, QList<QContact> listb)
+{
+ // NOTE: This compare is contact order insensitive.
+
+ // Remove matching contacts
+ foreach (QContact a, lista) {
+ foreach (QContact b, listb) {
+ if (compareContacts(a, b)) {
+ lista.removeOne(a);
+ listb.removeOne(b);
+ break;
+ }
+ }
+ }
+ return (lista.count() == 0 && listb.count() == 0);
+}
+
+bool tst_QContactAsync::compareContacts(QContact ca, QContact cb)
+{
+ // NOTE: This compare is contact detail order insensitive.
+
+ if (ca.localId() != cb.localId())
+ return false;
+
+ QList<QContactDetail> aDetails = ca.details();
+ QList<QContactDetail> bDetails = cb.details();
+
+ // Remove matching details
+ foreach (QContactDetail ad, aDetails) {
+ foreach (QContactDetail bd, bDetails) {
+ if (ad == bd) {
+ ca.removeDetail(&ad);
+ cb.removeDetail(&bd);
+ break;
+ }
+
+ // Special handling for timestamp
+ if (ad.definitionName() == QContactTimestamp::DefinitionName &&
+ bd.definitionName() == QContactTimestamp::DefinitionName) {
+ QContactTimestamp at = static_cast<QContactTimestamp>(ad);
+ QContactTimestamp bt = static_cast<QContactTimestamp>(bd);
+ if (at.created().toString() == bt.created().toString() &&
+ at.lastModified().toString() == bt.lastModified().toString()) {
+ ca.removeDetail(&ad);
+ cb.removeDetail(&bd);
+ break;
+ }
+
+ }
+ }
+ }
+ return (ca == cb);
+}
+
+bool tst_QContactAsync::containsIgnoringTimestamps(const QList<QContact>& list, const QContact& c)
+{
+ QList<QContact> cl = list;
+ QContact a(c);
+ for (int i = 0; i < cl.size(); i++) {
+ QContact b(cl.at(i));
+ if (compareIgnoringTimestamps(a, b))
+ return true;
+ }
+
+ return false;
+}
+
+bool tst_QContactAsync::compareIgnoringTimestamps(const QContact& ca, const QContact& cb)
+{
+ // Compares two contacts, ignoring any timestamp details
+ QContact a(ca);
+ QContact b(cb);
+ QList<QContactDetail> aDetails = a.details();
+ QList<QContactDetail> bDetails = b.details();
+
+ // They can be in any order, so loop
+ // First remove any matches, and any timestamps
+ foreach (QContactDetail d, aDetails) {
+ foreach (QContactDetail d2, bDetails) {
+ if (d == d2) {
+ a.removeDetail(&d);
+ b.removeDetail(&d2);
+ break;
+ }
+
+ if (d.definitionName() == QContactTimestamp::DefinitionName) {
+ a.removeDetail(&d);
+ }
+
+ if (d2.definitionName() == QContactTimestamp::DefinitionName) {
+ b.removeDetail(&d2);
+ }
+ }
+ }
+
+ if (a == b)
+ return true;
+ return false;
+}
+
+void tst_QContactAsync::testDestructor()
+{
+ QFETCH(QString, uri);
+ QContactManager* cm = prepareModel(uri);
+ QContactFetchRequest* req = new QContactFetchRequest;
+ req->setManager(cm);
+
+ QContactManager* cm2 = prepareModel(uri);
+ QContactFetchRequest* req2 = new QContactFetchRequest;
+ req2->setManager(cm2);
+
+ // first, delete manager then request
+ delete cm;
+ delete req;
+
+ // second, delete request then manager
+ delete req2;
+ delete cm2;
+}
+
+void tst_QContactAsync::deleteRequest()
+{
+ // Delete the sender (request) - check that it doesn't crash in this common coding error
+ delete sender();
+}
+
+void tst_QContactAsync::contactFetch()
+{
+ QFETCH(QString, uri);
+ QScopedPointer<QContactManager> cm(prepareModel(uri));
+
+ QContactFetchRequest cfr;
+ QVERIFY(cfr.type() == QContactAbstractRequest::ContactFetchRequest);
+
+ // initial state - not started, no manager.
+ QVERIFY(!cfr.isActive());
+ QVERIFY(!cfr.isFinished());
+ QVERIFY(!cfr.start());
+ QVERIFY(!cfr.cancel());
+ QVERIFY(!cfr.waitForFinished());
+
+ // "all contacts" retrieval
+ QContactFilter fil;
+ cfr.setManager(cm.data());
+ QCOMPARE(cfr.manager(), cm.data());
+ QVERIFY(!cfr.isActive());
+ QVERIFY(!cfr.isFinished());
+ QVERIFY(!cfr.cancel());
+ QVERIFY(!cfr.waitForFinished());
+ qRegisterMetaType<QContactFetchRequest*>("QContactFetchRequest*");
+ QThreadSignalSpy spy(&cfr, SIGNAL(stateChanged(QContactAbstractRequest::State)));
+ cfr.setFilter(fil);
+ QCOMPARE(cfr.filter(), fil);
+ QVERIFY(!cfr.cancel()); // not started
+
+ QVERIFY(cfr.start());
+ //QVERIFY(cfr.isFinished() || !cfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY((cfr.isActive() && cfr.state() == QContactAbstractRequest::ActiveState) || cfr.isFinished());
+ QVERIFY(cfr.waitForFinished());
+ QVERIFY(cfr.isFinished());
+
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QList<QContactLocalId> contactIds = cm->contactIds();
+ QList<QContact> contacts = cfr.contacts();
+ QCOMPARE(contactIds.size(), contacts.size());
+ for (int i = 0; i < contactIds.size(); i++) {
+ QContact curr = cm->contact(contactIds.at(i));
+ QVERIFY(contacts.at(i) == curr);
+ }
+
+ // asynchronous detail filtering
+ QContactDetailFilter dfil;
+ dfil.setDetailDefinitionName(QContactUrl::DefinitionName, QContactUrl::FieldUrl);
+ cfr.setFilter(dfil);
+ QVERIFY(cfr.filter() == dfil);
+ QVERIFY(!cfr.cancel()); // not started
+
+ QVERIFY(cfr.start());
+ QVERIFY((cfr.isActive() && cfr.state() == QContactAbstractRequest::ActiveState) || cfr.isFinished());
+ //QVERIFY(cfr.isFinished() || !cfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(cfr.waitForFinished());
+ QVERIFY(cfr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ contactIds = cm->contactIds(dfil);
+ contacts = cfr.contacts();
+ QCOMPARE(contactIds.size(), contacts.size());
+ for (int i = 0; i < contactIds.size(); i++) {
+ QContact curr = cm->contact(contactIds.at(i));
+ QVERIFY(contacts.at(i) == curr);
+ }
+
+ // sort order
+ QContactSortOrder sortOrder;
+ sortOrder.setDetailDefinitionName(QContactPhoneNumber::DefinitionName, QContactPhoneNumber::FieldNumber);
+ QList<QContactSortOrder> sorting;
+ sorting.append(sortOrder);
+ cfr.setFilter(fil);
+ cfr.setSorting(sorting);
+ QCOMPARE(cfr.sorting(), sorting);
+ QVERIFY(!cfr.cancel()); // not started
+ QVERIFY(cfr.start());
+ QVERIFY((cfr.isActive() && cfr.state() == QContactAbstractRequest::ActiveState) || cfr.isFinished());
+ //QVERIFY(cfr.isFinished() || !cfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(cfr.waitForFinished());
+ QVERIFY(cfr.isFinished());
+
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ contactIds = cm->contactIds(sorting);
+ contacts = cfr.contacts();
+ QCOMPARE(contactIds.size(), contacts.size());
+ for (int i = 0; i < contactIds.size(); i++) {
+ QContact curr = cm->contact(contactIds.at(i));
+ QVERIFY(contacts.at(i) == curr);
+ }
+
+ // restrictions
+ sorting.clear();
+ cfr.setFilter(fil);
+ cfr.setSorting(sorting);
+ QContactFetchHint fetchHint;
+ fetchHint.setDetailDefinitionsHint(QStringList(QContactName::DefinitionName));
+ cfr.setFetchHint(fetchHint);
+ QCOMPARE(cfr.fetchHint().detailDefinitionsHint(), QStringList(QContactName::DefinitionName));
+ QVERIFY(!cfr.cancel()); // not started
+ QVERIFY(cfr.start());
+ QVERIFY((cfr.isActive() && cfr.state() == QContactAbstractRequest::ActiveState) || cfr.isFinished());
+ //QVERIFY(cfr.isFinished() || !cfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(cfr.waitForFinished());
+ QVERIFY(cfr.isFinished());
+
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ contactIds = cm->contactIds(sorting);
+ contacts = cfr.contacts();
+ QCOMPARE(contactIds.size(), contacts.size());
+ for (int i = 0; i < contactIds.size(); i++) {
+ // create a contact from the restricted data only (id + display label)
+ QContact currFull = cm->contact(contactIds.at(i));
+ QContact currRestricted;
+ currRestricted.setId(currFull.id());
+ QList<QContactName> names = currFull.details<QContactName>();
+ foreach (const QContactName& name, names) {
+ QContactName fullName = name;
+ if (!fullName.isEmpty()) {
+ currRestricted.saveDetail(&fullName);
+ }
+ }
+
+ // now find the contact in the retrieved list which our restricted contact mimics
+ QContact retrievedRestricted;
+ bool found = false;
+ foreach (const QContact& retrieved, contacts) {
+ if (retrieved.id() == currRestricted.id()) {
+ retrievedRestricted = retrieved;
+ found = true;
+ }
+ }
+
+ QVERIFY(found); // must exist or fail.
+
+ // ensure that the contact is the same (except synth fields)
+ QList<QContactDetail> retrievedDetails = retrievedRestricted.details();
+ QList<QContactDetail> expectedDetails = currRestricted.details();
+ foreach (const QContactDetail& det, expectedDetails) {
+ // ignore backend synthesised details
+ // again, this requires a "default contact details" function to work properly.
+ if (det.definitionName() == QContactDisplayLabel::DefinitionName
+ || det.definitionName() == QContactTimestamp::DefinitionName) {
+ continue;
+ }
+
+ // everything else in the expected contact should be in the retrieved one.
+ QVERIFY(retrievedDetails.contains(det));
+ }
+ }
+
+ // cancelling
+ sorting.clear();
+ cfr.setFilter(fil);
+ cfr.setSorting(sorting);
+ cfr.setFetchHint(QContactFetchHint());
+
+ int bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT; // attempt to cancel 40 times. If it doesn't work due to threading, bail out.
+ while (true) {
+ QVERIFY(!cfr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(cfr.start());
+ if (!cfr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ spy.clear();
+ cfr.waitForFinished();
+ sorting.clear();
+ cfr.setFilter(fil);
+ cfr.setSorting(sorting);
+ cfr.setFetchHint(QContactFetchHint());
+ cfr.setFetchHint(QContactFetchHint());
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ continue;
+ }
+
+ // if we get here, then we are cancelling the request.
+ QVERIFY(cfr.waitForFinished());
+ QVERIFY(cfr.isCanceled());
+
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+ break;
+ }
+
+ // restart, and wait for progress after cancel.
+ while (true) {
+ QVERIFY(!cfr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(cfr.start());
+ if (!cfr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ cfr.waitForFinished();
+ sorting.clear();
+ cfr.setFilter(fil);
+ cfr.setSorting(sorting);
+ cfr.setFetchHint(QContactFetchHint());
+ bailoutCount -= 1;
+ spy.clear();
+ if (!bailoutCount) {
+ //qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ continue;
+ }
+ cfr.waitForFinished();
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+ QVERIFY(!cfr.isActive());
+ QVERIFY(cfr.state() == QContactAbstractRequest::CanceledState);
+ break;
+ }
+
+ // Now test deletion in the first slot called
+ QContactFetchRequest *cfr2 = new QContactFetchRequest();
+ QPointer<QObject> obj(cfr2);
+ cfr2->setManager(cm.data());
+ connect(cfr2, SIGNAL(resultsAvailable()), this, SLOT(deleteRequest()));
+ QVERIFY(cfr2->start());
+ int i = 100;
+ // at this point we can't even call wait for finished..
+ while(obj && i > 0) {
+ QTest::qWait(50); // force it to process events at least once.
+ i--;
+ }
+ QVERIFY(obj == NULL);
+}
+
+void tst_QContactAsync::contactIdFetch()
+{
+ QFETCH(QString, uri);
+ QScopedPointer<QContactManager> cm(prepareModel(uri));
+ QContactLocalIdFetchRequest cfr;
+ QVERIFY(cfr.type() == QContactAbstractRequest::ContactLocalIdFetchRequest);
+
+ // initial state - not started, no manager.
+ QVERIFY(!cfr.isActive());
+ QVERIFY(!cfr.isFinished());
+ QVERIFY(!cfr.start());
+ QVERIFY(!cfr.cancel());
+ QVERIFY(!cfr.waitForFinished());
+
+ // "all contacts" retrieval
+ QContactFilter fil;
+ cfr.setManager(cm.data());
+ QCOMPARE(cfr.manager(), cm.data());
+ QVERIFY(!cfr.isActive());
+ QVERIFY(!cfr.isFinished());
+ QVERIFY(!cfr.cancel());
+ QVERIFY(!cfr.waitForFinished());
+ qRegisterMetaType<QContactLocalIdFetchRequest*>("QContactLocalIdFetchRequest*");
+
+ QThreadSignalSpy spy(&cfr, SIGNAL(stateChanged(QContactAbstractRequest::State)));
+ cfr.setFilter(fil);
+ QCOMPARE(cfr.filter(), fil);
+ QVERIFY(!cfr.cancel()); // not started
+ QVERIFY(cfr.start());
+
+ QVERIFY((cfr.isActive() &&cfr.state() == QContactAbstractRequest::ActiveState) || cfr.isFinished());
+ //QVERIFY(cfr.isFinished() || !cfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(cfr.waitForFinished());
+ QVERIFY(cfr.isFinished());
+
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QList<QContactLocalId> contactIds = cm->contactIds();
+ QList<QContactLocalId> result = cfr.ids();
+ QCOMPARE(contactIds, result);
+
+ // asynchronous detail filtering
+ QContactDetailFilter dfil;
+ dfil.setDetailDefinitionName(QContactUrl::DefinitionName, QContactUrl::FieldUrl);
+ cfr.setFilter(dfil);
+ QVERIFY(cfr.filter() == dfil);
+ QVERIFY(!cfr.cancel()); // not started
+
+ QVERIFY(cfr.start());
+ QVERIFY((cfr.isActive() && cfr.state() == QContactAbstractRequest::ActiveState) || cfr.isFinished());
+ //QVERIFY(cfr.isFinished() || !cfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(cfr.waitForFinished());
+ QVERIFY(cfr.isFinished());
+
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ contactIds = cm->contactIds(dfil);
+ result = cfr.ids();
+ QCOMPARE(contactIds, result);
+
+ // sort order
+ QContactSortOrder sortOrder;
+ sortOrder.setDetailDefinitionName(QContactPhoneNumber::DefinitionName, QContactPhoneNumber::FieldNumber);
+ QList<QContactSortOrder> sorting;
+ sorting.append(sortOrder);
+ cfr.setFilter(fil);
+ cfr.setSorting(sorting);
+ QCOMPARE(cfr.sorting(), sorting);
+ QVERIFY(!cfr.cancel()); // not started
+ QVERIFY(cfr.start());
+ QVERIFY((cfr.isActive() && cfr.state() == QContactAbstractRequest::ActiveState) || cfr.isFinished());
+ //QVERIFY(cfr.isFinished() || !cfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(cfr.waitForFinished());
+ QVERIFY(cfr.isFinished());
+
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ contactIds = cm->contactIds(sorting);
+ result = cfr.ids();
+ QCOMPARE(contactIds, result);
+
+ // cancelling
+ sorting.clear();
+ cfr.setFilter(fil);
+ cfr.setSorting(sorting);
+
+ int bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT; // attempt to cancel 40 times. If it doesn't work due to threading, bail out.
+ while (true) {
+ QVERIFY(!cfr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(cfr.start());
+ if (!cfr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ cfr.waitForFinished();
+ sorting.clear();
+ cfr.setFilter(fil);
+ cfr.setSorting(sorting);
+ bailoutCount -= 1;
+ spy.clear();
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ continue;
+ }
+
+ // if we get here, then we are cancelling the request.
+ QVERIFY(cfr.waitForFinished());
+ QVERIFY(cfr.isCanceled());
+
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ break;
+ }
+
+ // restart, and wait for progress after cancel.
+ while (true) {
+ QVERIFY(!cfr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(cfr.start());
+ if (!cfr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ cfr.waitForFinished();
+ sorting.clear();
+ cfr.setFilter(fil);
+ cfr.setSorting(sorting);
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ continue;
+ }
+ cfr.waitForFinished();
+ QVERIFY(cfr.isCanceled());
+
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+ break;
+ }
+
+}
+
+void tst_QContactAsync::contactRemove()
+{
+ QFETCH(QString, uri);
+ QScopedPointer<QContactManager> cm(prepareModel(uri));
+ QContactRemoveRequest crr;
+ QVERIFY(crr.type() == QContactAbstractRequest::ContactRemoveRequest);
+
+ // initial state - not started, no manager.
+ QVERIFY(!crr.isActive());
+ QVERIFY(!crr.isFinished());
+ QVERIFY(!crr.start());
+ QVERIFY(!crr.cancel());
+ QVERIFY(!crr.waitForFinished());
+
+ // specific contact set
+ crr.setContactId(QContactLocalId(3));
+ QVERIFY(crr.contactIds() == QList<QContactLocalId>() << QContactLocalId(3));
+
+ // specific contact removal via detail filter
+ int originalCount = cm->contactIds().size();
+ QContactDetailFilter dfil;
+ dfil.setDetailDefinitionName(QContactUrl::DefinitionName, QContactUrl::FieldUrl);
+ crr.setContactIds(cm->contactIds(dfil));
+ crr.setManager(cm.data());
+ QCOMPARE(crr.manager(), cm.data());
+ QVERIFY(!crr.isActive());
+ QVERIFY(!crr.isFinished());
+ QVERIFY(!crr.cancel());
+ QVERIFY(!crr.waitForFinished());
+ qRegisterMetaType<QContactRemoveRequest*>("QContactRemoveRequest*");
+ QThreadSignalSpy spy(&crr, SIGNAL(stateChanged(QContactAbstractRequest::State)));
+ QVERIFY(!crr.cancel()); // not started
+
+ QVERIFY(!cm->contactIds(dfil).isEmpty());
+
+ QVERIFY(crr.start());
+
+ QVERIFY((crr.isActive() &&crr.state() == QContactAbstractRequest::ActiveState) || crr.isFinished());
+ //QVERIFY(crr.isFinished() || !crr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(crr.waitForFinished());
+ QVERIFY(crr.isFinished());
+
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QCOMPARE(cm->contactIds().size(), originalCount - 1);
+ QVERIFY(cm->contactIds(dfil).isEmpty());
+
+ // remove all contacts
+ dfil.setDetailDefinitionName(QContactDisplayLabel::DefinitionName); // delete everything.
+ crr.setContactIds(cm->contactIds(dfil));
+
+ QVERIFY(!crr.cancel()); // not started
+ QVERIFY(crr.start());
+
+ QVERIFY((crr.isActive() && crr.state() == QContactAbstractRequest::ActiveState) || crr.isFinished());
+ //QVERIFY(crr.isFinished() || !crr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(crr.waitForFinished());
+ QVERIFY(crr.isFinished());
+
+ QCOMPARE(cm->contactIds().size(), 0); // no contacts should be left.
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ // cancelling
+ QContact temp;
+ QContactName nameDetail;
+ nameDetail.setFirstName("Should not be removed");
+ temp.saveDetail(&nameDetail);
+ cm->saveContact(&temp);
+ crr.setContactIds(cm->contactIds(dfil));
+
+ int bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT; // attempt to cancel 40 times. If it doesn't work due to threading, bail out.
+ while (true) {
+ QVERIFY(!crr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(spy.count() == 0);
+ QVERIFY(crr.start());
+ if (!crr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ crr.waitForFinished();
+ crr.setContactIds(cm->contactIds(dfil));
+ temp.setId(QContactId());
+ if (!cm->saveContact(&temp)) {
+ QSKIP("Unable to save temporary contact for remove request cancellation test!", SkipSingle);
+ }
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+
+ // if we get here, then we are cancelling the request.
+ QVERIFY(crr.waitForFinished());
+ QVERIFY(crr.isCanceled());
+ QCOMPARE(cm->contactIds().size(), 1);
+ QCOMPARE(cm->contactIds(), crr.contactIds());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+ break;
+ }
+
+ // restart, and wait for progress after cancel.
+ while (true) {
+ QVERIFY(!crr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(crr.start());
+ if (!crr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ crr.waitForFinished();
+ crr.setContactIds(cm->contactIds(dfil));
+ temp.setId(QContactId());
+ cm->saveContact(&temp);
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+ crr.waitForFinished();
+ QVERIFY(crr.isCanceled());
+ QCOMPARE(cm->contactIds().size(), 1);
+ QCOMPARE(cm->contactIds(), crr.contactIds());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+ break;
+ }
+
+}
+
+void tst_QContactAsync::contactSave()
+{
+ QFETCH(QString, uri);
+ QScopedPointer<QContactManager> cm(prepareModel(uri));
+ QContactSaveRequest csr;
+ QVERIFY(csr.type() == QContactAbstractRequest::ContactSaveRequest);
+
+ // initial state - not started, no manager.
+ QVERIFY(!csr.isActive());
+ QVERIFY(!csr.isFinished());
+ QVERIFY(!csr.start());
+ QVERIFY(!csr.cancel());
+ QVERIFY(!csr.waitForFinished());
+
+ // save a new contact
+ int originalCount = cm->contactIds().size();
+ QContact testContact;
+ QContactName nameDetail;
+ nameDetail.setFirstName("Test Contact");
+ testContact.saveDetail(&nameDetail);
+ QList<QContact> saveList;
+ saveList << testContact;
+ csr.setManager(cm.data());
+ QCOMPARE(csr.manager(), cm.data());
+ QVERIFY(!csr.isActive());
+ QVERIFY(!csr.isFinished());
+ QVERIFY(!csr.cancel());
+ QVERIFY(!csr.waitForFinished());
+ qRegisterMetaType<QContactSaveRequest*>("QContactSaveRequest*");
+ QThreadSignalSpy spy(&csr, SIGNAL(stateChanged(QContactAbstractRequest::State)));
+ csr.setContact(testContact);
+ QCOMPARE(csr.contacts(), saveList);
+ QVERIFY(!csr.cancel()); // not started
+ QVERIFY(csr.start());
+
+ QVERIFY((csr.isActive() && csr.state() == QContactAbstractRequest::ActiveState) || csr.isFinished());
+ //QVERIFY(csr.isFinished() || !csr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(csr.waitForFinished());
+ QVERIFY(csr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QList<QContact> expected = csr.contacts();
+ QCOMPARE(expected.size(), 1);
+ QList<QContact> result;
+ result << cm->contact(expected.first().id().localId());
+ //some backends add extra fields, so this doesn't work:
+ //QCOMPARE(result, expected);
+ // XXX: really, we should use isSuperset() from tst_QContactManager, but this will do for now:
+ QVERIFY(result.first().detail<QContactName>() == nameDetail);
+ QCOMPARE(cm->contactIds().size(), originalCount + 1);
+
+ // update a previously saved contact
+ QContactPhoneNumber phn;
+ phn.setNumber("12345678");
+ testContact = result.first();
+ testContact.saveDetail(&phn);
+ saveList.clear();
+ saveList << testContact;
+ csr.setContacts(saveList);
+ QCOMPARE(csr.contacts(), saveList);
+ QVERIFY(!csr.cancel()); // not started
+ QVERIFY(csr.start());
+
+ QVERIFY((csr.isActive() && csr.state() == QContactAbstractRequest::ActiveState) || csr.isFinished());
+ //QVERIFY(csr.isFinished() || !csr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(csr.waitForFinished());
+
+ QVERIFY(csr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ expected = csr.contacts();
+ result.clear();
+ result << cm->contact(expected.first().id().localId());
+ //QVERIFY(compareContactLists(result, expected));
+
+ //here we can't compare the whole contact details, testContact would be updated by async call because we just use QThreadSignalSpy to receive signals.
+ //QVERIFY(containsIgnoringTimestamps(result, testContact));
+ // XXX: really, we should use isSuperset() from tst_QContactManager, but this will do for now:
+ QVERIFY(result.first().detail<QContactPhoneNumber>().number() == phn.number());
+
+ QCOMPARE(cm->contactIds().size(), originalCount + 1);
+
+ // cancelling
+ QContact temp = testContact;
+ QContactUrl url;
+ url.setUrl("should not get saved");
+ temp.saveDetail(&url);
+ saveList.clear();
+ saveList << temp;
+ csr.setContacts(saveList);
+
+ int bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT; // attempt to cancel 40 times. If it doesn't work due to threading, bail out.
+ while (true) {
+ QVERIFY(!csr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(csr.start());
+ if (!csr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ csr.waitForFinished();
+ saveList = csr.contacts();
+ if (cm->contactIds().size() > (originalCount + 1) && !cm->removeContact(saveList.at(0).localId())) {
+ QSKIP("Unable to remove saved contact to test cancellation of contact save request", SkipSingle);
+ }
+ saveList.clear();
+ saveList << temp;
+ csr.setContacts(saveList);
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+
+ // if we get here, then we are cancelling the request.
+ QVERIFY(csr.waitForFinished());
+ QVERIFY(csr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ // verify that the changes were not saved
+ expected.clear();
+ QList<QContactLocalId> allContacts = cm->contactIds();
+ for (int i = 0; i < allContacts.size(); i++) {
+ expected.append(cm->contact(allContacts.at(i)));
+ }
+ QVERIFY(!expected.contains(temp));
+ QCOMPARE(cm->contactIds().size(), originalCount + 1);
+ break;
+ }
+ // restart, and wait for progress after cancel.
+
+ while (true) {
+ QVERIFY(!csr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(csr.start());
+ if (!csr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ csr.waitForFinished();
+ saveList = csr.contacts();
+ if (cm->contactIds().size() > (originalCount + 1) && !cm->removeContact(saveList.at(0).localId())) {
+ QSKIP("Unable to remove saved contact to test cancellation of contact save request", SkipSingle);
+ }
+ saveList.clear();
+ saveList << temp;
+ csr.setContacts(saveList);
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+ csr.waitForFinished(); // now wait until finished (if it hasn't already).
+ QVERIFY(csr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ // verify that the changes were not saved
+ expected.clear();
+ QList<QContactLocalId> allContacts = cm->contactIds();
+ for (int i = 0; i < allContacts.size(); i++) {
+ expected.append(cm->contact(allContacts.at(i)));
+ }
+ QVERIFY(!expected.contains(temp));
+ QCOMPARE(cm->contactIds().size(), originalCount + 1);
+ break;
+ }
+}
+
+void tst_QContactAsync::definitionFetch()
+{
+ QFETCH(QString, uri);
+ QScopedPointer<QContactManager> cm(prepareModel(uri));
+ QContactDetailDefinitionFetchRequest dfr;
+ QVERIFY(dfr.type() == QContactAbstractRequest::DetailDefinitionFetchRequest);
+ QVERIFY(dfr.contactType() == QString(QLatin1String(QContactType::TypeContact))); // ensure ctor sets contact type correctly.
+ dfr.setContactType(QContactType::TypeContact);
+ QVERIFY(dfr.contactType() == QString(QLatin1String(QContactType::TypeContact)));
+
+ // initial state - not started, no manager.
+ QVERIFY(!dfr.isActive());
+ QVERIFY(!dfr.isFinished());
+ QVERIFY(!dfr.start());
+ QVERIFY(!dfr.cancel());
+ QVERIFY(!dfr.waitForFinished());
+
+ // "all definitions" retrieval
+ dfr.setManager(cm.data());
+ QCOMPARE(dfr.manager(), cm.data());
+ QVERIFY(!dfr.isActive());
+ QVERIFY(!dfr.isFinished());
+ QVERIFY(!dfr.cancel());
+ QVERIFY(!dfr.waitForFinished());
+ qRegisterMetaType<QContactDetailDefinitionFetchRequest*>("QContactDetailDefinitionFetchRequest*");
+ QThreadSignalSpy spy(&dfr, SIGNAL(stateChanged(QContactAbstractRequest::State)));
+ dfr.setDefinitionNames(QStringList());
+ QVERIFY(!dfr.cancel()); // not started
+ QVERIFY(dfr.start());
+
+ QVERIFY((dfr.isActive() && dfr.state() == QContactAbstractRequest::ActiveState) || dfr.isFinished());
+ //QVERIFY(dfr.isFinished() || !dfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(dfr.waitForFinished());
+ QVERIFY(dfr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QMap<QString, QContactDetailDefinition> defs = cm->detailDefinitions();
+ QMap<QString, QContactDetailDefinition> result = dfr.definitions();
+ QCOMPARE(defs, result);
+
+ // specific definition retrieval
+ QStringList specific;
+ specific << QContactUrl::DefinitionName;
+ dfr.setDefinitionName(QContactUrl::DefinitionName);
+ QVERIFY(dfr.definitionNames() == specific);
+ QVERIFY(!dfr.cancel()); // not started
+ QVERIFY(dfr.start());
+
+ QVERIFY((dfr.isActive() && dfr.state() == QContactAbstractRequest::ActiveState) || dfr.isFinished());
+ //QVERIFY(dfr.isFinished() || !dfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(dfr.waitForFinished());
+ QVERIFY(dfr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ defs.clear();
+ defs.insert(QContactUrl::DefinitionName, cm->detailDefinition(QContactUrl::DefinitionName));
+ result = dfr.definitions();
+ QCOMPARE(defs, result);
+
+ // cancelling
+ dfr.setDefinitionNames(QStringList());
+
+ int bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT; // attempt to cancel 40 times. If it doesn't work due to threading, bail out.
+ while (true) {
+ QVERIFY(!dfr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(dfr.start());
+ if (!dfr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ dfr.waitForFinished();
+ dfr.setDefinitionNames(QStringList());
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+
+ // if we get here, then we are cancelling the request.
+ QVERIFY(dfr.waitForFinished());
+ QVERIFY(dfr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+ break;
+ }
+
+ // restart, and wait for progress after cancel.
+ while (true) {
+ QVERIFY(!dfr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(dfr.start());
+ if (!dfr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ dfr.waitForFinished();
+ dfr.setDefinitionNames(QStringList());
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+ dfr.waitForFinished();
+ QVERIFY(dfr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ break;
+ }
+
+}
+
+void tst_QContactAsync::definitionRemove()
+{
+ QFETCH(QString, uri);
+
+ QScopedPointer<QContactManager> cm(prepareModel(uri));
+ if (!cm->hasFeature(QContactManager::MutableDefinitions)) {
+ QSKIP("This contact manager does not support mutable definitions, can't remove a definition!", SkipSingle);
+ }
+ QContactDetailDefinitionRemoveRequest drr;
+ QVERIFY(drr.type() == QContactAbstractRequest::DetailDefinitionRemoveRequest);
+ QVERIFY(drr.contactType() == QString(QLatin1String(QContactType::TypeContact))); // ensure ctor sets contact type correctly.
+ drr.setContactType(QContactType::TypeContact);
+ drr.setDefinitionNames(QStringList());
+ QVERIFY(drr.contactType() == QString(QLatin1String(QContactType::TypeContact)));
+
+ // initial state - not started, no manager.
+ QVERIFY(!drr.isActive());
+ QVERIFY(!drr.isFinished());
+ QVERIFY(!drr.start());
+ QVERIFY(!drr.cancel());
+ QVERIFY(!drr.waitForFinished());
+
+ // specific group removal
+ int originalCount = cm->detailDefinitions().keys().size();
+ QStringList removeIds;
+ removeIds << cm->detailDefinitions().keys().first();
+ drr.setDefinitionName(cm->detailDefinitions().keys().first());
+ drr.setManager(cm.data());
+ QCOMPARE(drr.manager(), cm.data());
+ QVERIFY(!drr.isActive());
+ QVERIFY(!drr.isFinished());
+ QVERIFY(!drr.cancel());
+ QVERIFY(!drr.waitForFinished());
+ qRegisterMetaType<QContactDetailDefinitionRemoveRequest*>("QContactDetailDefinitionRemoveRequest*");
+ QThreadSignalSpy spy(&drr, SIGNAL(stateChanged(QContactAbstractRequest::State)));
+ QVERIFY(drr.definitionNames() == removeIds);
+ QVERIFY(!drr.cancel()); // not started
+ QVERIFY(drr.start());
+
+ QVERIFY((drr.isActive() && drr.state() == QContactAbstractRequest::ActiveState) || drr.isFinished());
+ //QVERIFY(drr.isFinished() || !drr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(drr.waitForFinished());
+ QVERIFY(drr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 1);
+ cm->detailDefinition(removeIds.first()); // check that it has already been removed.
+ QCOMPARE(cm->error(), QContactManager::DoesNotExistError);
+
+ // remove (asynchronously) a nonexistent group - should fail.
+ drr.setDefinitionNames(removeIds);
+ QVERIFY(!drr.cancel()); // not started
+ QVERIFY(drr.start());
+
+ QVERIFY((drr.isActive() && drr.state() == QContactAbstractRequest::ActiveState) || drr.isFinished());
+ //QVERIFY(drr.isFinished() || !drr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(drr.waitForFinished());
+ QVERIFY(drr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 1); // hasn't changed
+ QCOMPARE(drr.error(), QContactManager::DoesNotExistError);
+
+ // remove with list containing one valid and one invalid id.
+ removeIds << cm->detailDefinitions().keys().first();
+ drr.setDefinitionNames(removeIds);
+ QVERIFY(!drr.cancel()); // not started
+ QVERIFY(drr.start());
+
+ QVERIFY((drr.isActive() && drr.state() == QContactAbstractRequest::ActiveState) || drr.isFinished());
+ //QVERIFY(drr.isFinished() || !drr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(drr.waitForFinished());
+ QVERIFY(drr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished signals
+ spy.clear();
+
+ QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 2); // only one more has been removed
+ QVERIFY(drr.errorMap().count() == 1);
+ QVERIFY(drr.errorMap().keys().contains(0));
+ QCOMPARE(drr.errorMap().value(0), QContactManager::DoesNotExistError);
+
+ // remove with empty list - nothing should happen.
+ removeIds.clear();
+ drr.setDefinitionNames(removeIds);
+ QVERIFY(!drr.cancel()); // not started
+ QVERIFY(drr.start());
+
+ QVERIFY(drr.isActive() || drr.isFinished());
+ //QVERIFY(drr.isFinished() || !drr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(drr.waitForFinished());
+
+ QVERIFY(drr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 2); // hasn't changed
+ QCOMPARE(drr.error(), QContactManager::NoError); // no error but no effect.
+
+ // cancelling
+ removeIds.clear();
+ removeIds << cm->detailDefinitions().keys().first();
+ drr.setDefinitionNames(removeIds);
+
+ int bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT; // attempt to cancel 40 times. If it doesn't work due to threading, bail out.
+ while (true) {
+ QVERIFY(!drr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(drr.start());
+ if (!drr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ drr.waitForFinished();
+ drr.setDefinitionNames(removeIds);
+
+ QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 3); // finished
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ // XXX should be readded
+ continue;
+ }
+
+ // if we get here, then we are cancelling the request.
+ QVERIFY(drr.waitForFinished());
+ QVERIFY(drr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 2); // hasn't changed
+ break;
+ }
+
+ // restart, and wait for progress after cancel.
+ while (true) {
+ QVERIFY(!drr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(drr.start());
+ if (!drr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ drr.waitForFinished();
+ drr.setDefinitionNames(removeIds);
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+ drr.waitForFinished();
+ QVERIFY(drr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ QCOMPARE(cm->detailDefinitions().keys().size(), originalCount - 3); // hasn't changed
+ break;
+ }
+
+}
+
+void tst_QContactAsync::definitionSave()
+{
+ QFETCH(QString, uri);
+
+ QScopedPointer<QContactManager> cm(prepareModel(uri));
+
+ if (!cm->hasFeature(QContactManager::MutableDefinitions)) {
+
+ QSKIP("This contact manager does not support mutable definitions, can't save a definition!", SkipSingle);
+ }
+
+ QContactDetailDefinitionSaveRequest dsr;
+ QVERIFY(dsr.type() == QContactAbstractRequest::DetailDefinitionSaveRequest);
+ QVERIFY(dsr.contactType() == QString(QLatin1String(QContactType::TypeContact))); // ensure ctor sets contact type correctly
+ dsr.setContactType(QContactType::TypeContact);
+ QVERIFY(dsr.contactType() == QString(QLatin1String(QContactType::TypeContact)));
+
+ // initial state - not started, no manager.
+ QVERIFY(!dsr.isActive());
+ QVERIFY(!dsr.isFinished());
+ QVERIFY(!dsr.start());
+ QVERIFY(!dsr.cancel());
+ QVERIFY(!dsr.waitForFinished());
+
+ // save a new detail definition
+ int originalCount = cm->detailDefinitions().keys().size();
+ QContactDetailDefinition testDef;
+ testDef.setName("TestDefinitionId");
+ QMap<QString, QContactDetailFieldDefinition> fields;
+ QContactDetailFieldDefinition f;
+ f.setDataType(QVariant::String);
+ fields.insert("TestDefinitionField", f);
+ testDef.setFields(fields);
+ QList<QContactDetailDefinition> saveList;
+ saveList << testDef;
+ dsr.setManager(cm.data());
+ QCOMPARE(dsr.manager(), cm.data());
+ QVERIFY(!dsr.isActive());
+ QVERIFY(!dsr.isFinished());
+ QVERIFY(!dsr.cancel());
+ QVERIFY(!dsr.waitForFinished());
+ qRegisterMetaType<QContactDetailDefinitionSaveRequest*>("QContactDetailDefinitionSaveRequest*");
+ QThreadSignalSpy spy(&dsr, SIGNAL(stateChanged(QContactAbstractRequest::State)));
+ dsr.setDefinition(testDef);
+ QCOMPARE(dsr.definitions(), saveList);
+ QVERIFY(!dsr.cancel()); // not started
+ QVERIFY(dsr.start());
+
+ QVERIFY((dsr.isActive() && dsr.state() == QContactAbstractRequest::ActiveState) || dsr.isFinished());
+ //QVERIFY(dsr.isFinished() || !dsr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(dsr.waitForFinished());
+ QVERIFY(dsr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QList<QContactDetailDefinition> expected;
+ expected << cm->detailDefinition("TestDefinitionId");
+ QList<QContactDetailDefinition> result = dsr.definitions();
+ QCOMPARE(expected, result);
+ QVERIFY(expected.contains(testDef));
+ QCOMPARE(cm->detailDefinitions().values().size(), originalCount + 1);
+
+ // update a previously saved group
+ fields.insert("TestDefinitionFieldTwo", f);
+ testDef.setFields(fields);
+ saveList.clear();
+ saveList << testDef;
+ dsr.setDefinitions(saveList);
+ QCOMPARE(dsr.definitions(), saveList);
+ QVERIFY(!dsr.cancel()); // not started
+ QVERIFY(dsr.start());
+
+ QVERIFY((dsr.isActive() && dsr.state() == QContactAbstractRequest::ActiveState) || dsr.isFinished());
+ //QVERIFY(dsr.isFinished() || !dsr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(dsr.waitForFinished());
+ QVERIFY(dsr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ expected.clear();
+ expected << cm->detailDefinition("TestDefinitionId");
+ result = dsr.definitions();
+ QCOMPARE(expected, result);
+ QVERIFY(expected.contains(testDef));
+ QCOMPARE(cm->detailDefinitions().values().size(), originalCount + 1);
+
+ // cancelling
+ fields.insert("TestDefinitionFieldThree - shouldn't get saved", f);
+ testDef.setFields(fields);
+ saveList.clear();
+ saveList << testDef;
+ dsr.setDefinitions(saveList);
+
+ int bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT; // attempt to cancel 40 times. If it doesn't work due to threading, bail out.
+ while (true) {
+ QVERIFY(!dsr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(dsr.start());
+ if (!dsr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ dsr.waitForFinished();
+ saveList.clear();
+ saveList << testDef;
+ dsr.setDefinitions(saveList);
+ cm->removeDetailDefinition(testDef.name());
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+
+ // if we get here, then we are cancelling the request.
+ QVERIFY(dsr.waitForFinished());
+ QVERIFY(dsr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ // verify that the changes were not saved
+ QList<QContactDetailDefinition> allDefs = cm->detailDefinitions().values();
+ QVERIFY(!allDefs.contains(testDef));
+ QCOMPARE(cm->detailDefinitions().values().size(), originalCount + 1);
+
+ break;
+ }
+
+ // restart, and wait for progress after cancel.
+ while (true) {
+ QVERIFY(!dsr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(dsr.start());
+ if (!dsr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ dsr.waitForFinished();
+ saveList.clear();
+ saveList << testDef;
+ dsr.setDefinitions(saveList);
+ cm->removeDetailDefinition(testDef.name());
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+ dsr.waitForFinished();
+ QVERIFY(dsr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ // verify that the changes were not saved
+ QList<QContactDetailDefinition> allDefs = cm->detailDefinitions().values();
+ QVERIFY(!allDefs.contains(testDef));
+ QCOMPARE(cm->detailDefinitions().values().size(), originalCount + 1);
+
+ break;
+ }
+
+}
+
+void tst_QContactAsync::relationshipFetch()
+{
+ QFETCH(QString, uri);
+ QScopedPointer<QContactManager> cm(prepareModel(uri));
+
+ if (!cm->hasFeature(QContactManager::Relationships)) {
+ QSKIP("This contact manager does not support relationships!", SkipSingle);
+ }
+
+ if (cm->managerName() == "symbian") {
+ QSKIP("This contact manager does not support the required relationship types for this test to pass!", SkipSingle);
+ }
+
+ QContactRelationshipFetchRequest rfr;
+ QVERIFY(rfr.type() == QContactAbstractRequest::RelationshipFetchRequest);
+
+ // initial state - not started, no manager.
+ QVERIFY(!rfr.isActive());
+ QVERIFY(!rfr.isFinished());
+ QVERIFY(!rfr.start());
+ QVERIFY(!rfr.cancel());
+ QVERIFY(!rfr.waitForFinished());
+
+ // "all relationships" retrieval
+ rfr.setManager(cm.data());
+ QCOMPARE(rfr.manager(), cm.data());
+ QVERIFY(!rfr.isActive());
+ QVERIFY(!rfr.isFinished());
+ QVERIFY(!rfr.cancel());
+ QVERIFY(!rfr.waitForFinished());
+ qRegisterMetaType<QContactRelationshipFetchRequest*>("QContactRelationshipFetchRequest*");
+ QThreadSignalSpy spy(&rfr, SIGNAL(stateChanged(QContactAbstractRequest::State)));
+ QVERIFY(!rfr.cancel()); // not started
+ QVERIFY(rfr.start());
+
+ QVERIFY((rfr.isActive() && rfr.state() == QContactAbstractRequest::ActiveState) || rfr.isFinished());
+ //QVERIFY(rfr.isFinished() || !rfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(rfr.waitForFinished());
+
+ QVERIFY(rfr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QList<QContactRelationship> rels = cm->relationships();
+ QList<QContactRelationship> result = rfr.relationships();
+ QCOMPARE(rels, result);
+
+ // specific relationship type retrieval
+ rfr.setRelationshipType(QContactRelationship::HasManager);
+ QVERIFY(!rfr.cancel()); // not started
+ QVERIFY(rfr.start());
+
+ QVERIFY((rfr.isActive() && rfr.state() == QContactAbstractRequest::ActiveState) || rfr.isFinished());
+ //QVERIFY(rfr.isFinished() || !rfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(rfr.waitForFinished());
+ QVERIFY(rfr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ rels = cm->relationships(QContactRelationship::HasManager);
+ result = rfr.relationships();
+ QCOMPARE(rels, result);
+
+ // specific source contact retrieval
+ rfr.setRelationshipType(QString());
+ QList<QContactLocalId> contacts = cm->contactIds();
+ QContactId aId;
+ foreach (const QContactLocalId& currId, contacts) {
+ QContact curr = cm->contact(currId);
+ if (curr.detail(QContactName::DefinitionName).value(QContactName::FieldFirstName) == QString("Aaron")) {
+ aId = curr.id();
+ break;
+ }
+ }
+ rfr.setFirst(aId);
+ QVERIFY(!rfr.cancel()); // not started
+ QVERIFY(rfr.start());
+
+ QVERIFY((rfr.isActive() && rfr.state() == QContactAbstractRequest::ActiveState) || rfr.isFinished());
+ //QVERIFY(rfr.isFinished() || !rfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(rfr.waitForFinished());
+ QVERIFY(rfr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ rels = cm->relationships(aId, QContactRelationship::First);
+ result = rfr.relationships();
+ QCOMPARE(rels, result);
+
+ // specific participant retrieval #1 - destination participant
+ rfr.setFirst(QContactId());
+ contacts = cm->contactIds();
+ QContactId bId;
+ foreach (const QContactLocalId& currId, contacts) {
+ QContact curr = cm->contact(currId);
+ if (curr.detail(QContactName::DefinitionName).value(QContactName::FieldFirstName) == QString("Bob")) {
+ bId = curr.id();
+ break;
+ }
+ }
+ rfr.setSecond(bId);
+
+ QVERIFY(!rfr.cancel()); // not started
+ QVERIFY(rfr.start());
+
+ QVERIFY((rfr.isActive() && rfr.state() == QContactAbstractRequest::ActiveState) || rfr.isFinished());
+ //QVERIFY(rfr.isFinished() || !rfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(rfr.waitForFinished());
+ QVERIFY(rfr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ // retrieve rels where second = id of B, and ensure that we get the same results
+ rels = cm->relationships(bId, QContactRelationship::Second);
+ result = rfr.relationships();
+ QCOMPARE(rels, result);
+
+ // specific participant retrieval #2 - source participant
+ rfr.setFirst(QContactId());
+ contacts = cm->contactIds();
+ QContactId cId;
+ foreach (const QContactLocalId& currId, contacts) {
+ QContact curr = cm->contact(currId);
+ if (curr.detail(QContactName::DefinitionName).value(QContactName::FieldFirstName) == QString("Borris")) {
+ cId = curr.id();
+ break;
+ }
+ }
+ rfr.setSecond(cId);
+
+ QVERIFY(!rfr.cancel()); // not started
+ QVERIFY(rfr.start());
+
+ QVERIFY((rfr.isActive() && rfr.state() == QContactAbstractRequest::ActiveState) || rfr.isFinished());
+ //QVERIFY(rfr.isFinished() || !rfr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(rfr.waitForFinished());
+ QVERIFY(rfr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ // retrieve rels where first = id of C and compare the results
+ rfr.setFirst(cId);
+ rfr.setSecond(QContactId());
+ QVERIFY(rfr.start());
+ QVERIFY(rfr.waitForFinished());
+ result = rfr.relationships();
+ rels = cm->relationships(cId, QContactRelationship::First);
+ QCOMPARE(rels, result);
+
+ // cancelling
+ rfr.setRelationshipType(QString());
+
+ int bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT; // attempt to cancel 40 times. If it doesn't work due to threading, bail out.
+ while (true) {
+ QVERIFY(!rfr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(rfr.start());
+ if (!rfr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ rfr.waitForFinished();
+ rfr.setRelationshipType(QString());
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+
+ // if we get here, then we are cancelling the request.
+ QVERIFY(rfr.waitForFinished());
+ QVERIFY(rfr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+ break;
+ }
+
+ // restart, and wait for progress after cancel.
+ while (true) {
+ QVERIFY(!rfr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(rfr.start());
+ if (!rfr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ rfr.waitForFinished();
+ rfr.setRelationshipType(QString());
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+ rfr.waitForFinished();
+ QVERIFY(rfr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+ break;
+ }
+}
+
+void tst_QContactAsync::relationshipRemove()
+{
+ QFETCH(QString, uri);
+ QScopedPointer<QContactManager> cm(prepareModel(uri));
+
+ if (!cm->hasFeature(QContactManager::Relationships)) {
+ QSKIP("This contact manager does not support relationships!", SkipSingle);
+ }
+
+ if (cm->managerName() == "symbian") {
+ QSKIP("This contact manager does not support the required relationship types for this test to pass!", SkipSingle);
+ }
+
+ QContactRelationshipRemoveRequest rrr;
+ QVERIFY(rrr.type() == QContactAbstractRequest::RelationshipRemoveRequest);
+
+ // initial state - not started, no manager.
+ QVERIFY(!rrr.isActive());
+ QVERIFY(!rrr.isFinished());
+ QVERIFY(!rrr.start());
+ QVERIFY(!rrr.cancel());
+ QVERIFY(!rrr.waitForFinished());
+
+ QList<QContactLocalId> contacts = cm->contactIds();
+ QContactId aId, bId, cId;
+ foreach (const QContactLocalId& currId, contacts) {
+ QContact curr = cm->contact(currId);
+ if (curr.detail(QContactName::DefinitionName).value(QContactName::FieldFirstName) == QString("Aaron")) {
+ aId = curr.id();
+ continue;
+ }
+ if (curr.detail(QContactName::DefinitionName).value(QContactName::FieldFirstName) == QString("Bob")) {
+ bId = curr.id();
+ continue;
+ }
+ if (curr.detail(QContactName::DefinitionName).value(QContactName::FieldFirstName) == QString("Borris")) {
+ cId = curr.id();
+ continue;
+ }
+ }
+
+ // specific source, destination and type removal
+ QList<QContactRelationship> relationships;
+ QContactRelationship r;
+ r.setFirst(aId);
+ r.setSecond(cId);
+ r.setRelationshipType(QContactRelationship::HasAssistant);
+ relationships.push_back(r);
+
+ rrr.setRelationships(relationships);
+ rrr.setManager(cm.data());
+ qRegisterMetaType<QContactRelationshipRemoveRequest*>("QContactRelationshipRemoveRequest*");
+ QThreadSignalSpy spy(&rrr, SIGNAL(stateChanged(QContactAbstractRequest::State)));
+ QCOMPARE(rrr.manager(), cm.data());
+ QVERIFY(!rrr.isActive());
+ QVERIFY(!rrr.isFinished());
+ QVERIFY(!rrr.cancel());
+ QVERIFY(!rrr.waitForFinished());
+
+ QVERIFY(!rrr.cancel()); // not started
+ QVERIFY(rrr.start());
+
+ QVERIFY((rrr.isActive() && rrr.state() == QContactAbstractRequest::ActiveState) || rrr.isFinished());
+ //QVERIFY(rrr.isFinished() || !rrr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(rrr.waitForFinished());
+ QVERIFY(rrr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+ QCOMPARE(cm->relationships(QContactRelationship::HasAssistant, cId, QContactRelationship::Second).size(), 1);
+
+ // remove (asynchronously) a nonexistent relationship - should fail.
+ r.setFirst(cId);
+ r.setSecond(aId);
+ r.setRelationshipType(QContactRelationship::HasManager);
+ relationships.clear();
+ relationships.push_back(r);
+ rrr.setRelationship(r);
+ QVERIFY(rrr.relationships() == relationships);
+ rrr.setManager(cm.data());
+ QVERIFY(!rrr.cancel()); // not started
+ QVERIFY(rrr.start());
+
+ QVERIFY((rrr.isActive() && rrr.state() == QContactAbstractRequest::ActiveState) || rrr.isFinished());
+ //QVERIFY(rrr.isFinished() || !rrr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(rrr.waitForFinished());
+ QVERIFY(rrr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QCOMPARE(cm->relationships(QContactRelationship::HasManager, cId, QContactRelationship::First).size(), 0);
+// QCOMPARE(rrr.error(), QContactManager::DoesNotExistError);
+
+ // cancelling
+ r.setFirst(cId);
+ r.setSecond(QContactId());
+ relationships.clear();
+ relationships.push_back(r);
+
+
+ int bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT; // attempt to cancel 40 times. If it doesn't work due to threading, bail out.
+ while (true) {
+ QVERIFY(!rrr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(rrr.start());
+ if (!rrr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ rrr.waitForFinished();
+ rrr.setRelationships(relationships);
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+
+ // if we get here, then we are cancelling the request.
+ QVERIFY(rrr.waitForFinished());
+ QVERIFY(rrr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ QVERIFY(cm->relationships(cId).size() != 0); // didn't remove them.
+ break;
+ }
+
+ // restart, and wait for progress after cancel.
+ while (true) {
+ QVERIFY(!rrr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(rrr.start());
+ if (!rrr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ rrr.waitForFinished();
+ rrr.setRelationships(relationships);
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+ rrr.waitForFinished();
+ QVERIFY(rrr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ QVERIFY(cm->relationships(cId).size() != 0); // didn't remove them.
+ break;
+ }
+}
+
+void tst_QContactAsync::relationshipSave()
+{
+ QFETCH(QString, uri);
+ QScopedPointer<QContactManager> cm(prepareModel(uri));
+
+ if (!cm->hasFeature(QContactManager::Relationships)) {
+ QSKIP("This contact manager does not support relationships!", SkipSingle);
+ }
+
+ if (cm->managerName() == "symbian") {
+ QSKIP("This contact manager does not support the required relationship types for this test to pass!", SkipSingle);
+ }
+
+ QContactRelationshipSaveRequest rsr;
+ QVERIFY(rsr.type() == QContactAbstractRequest::RelationshipSaveRequest);
+
+ // initial state - not started, no manager.
+ QVERIFY(!rsr.isActive());
+ QVERIFY(!rsr.isFinished());
+ QVERIFY(!rsr.start());
+ QVERIFY(!rsr.cancel());
+ QVERIFY(!rsr.waitForFinished());
+
+ QList<QContactLocalId> contacts = cm->contactIds();
+ QContactId cId, aId, bId;
+ foreach (const QContactLocalId& currId, contacts) {
+ QContact curr = cm->contact(currId);
+ if (curr.detail(QContactName::DefinitionName).value(QContactName::FieldFirstName) == QString("Borris")) {
+ cId = curr.id();
+ } else if (curr.detail(QContactName::DefinitionName).value(QContactName::FieldFirstName) == QString("Bob")) {
+ bId = curr.id();
+ } else if (curr.detail(QContactName::DefinitionName).value(QContactName::FieldFirstName) == QString("Aaron")) {
+ aId = curr.id();
+ }
+ }
+
+ // save a new relationship
+ int originalCount = cm->relationships(aId).size();
+ QContactRelationship testRel;
+ testRel.setFirst(aId);
+ testRel.setRelationshipType(QContactRelationship::HasSpouse);
+ testRel.setSecond(bId);
+ QList<QContactRelationship> saveList;
+ saveList << testRel;
+ rsr.setManager(cm.data());
+ QCOMPARE(rsr.manager(), cm.data());
+ QVERIFY(!rsr.isActive());
+ QVERIFY(!rsr.isFinished());
+ QVERIFY(!rsr.cancel());
+ QVERIFY(!rsr.waitForFinished());
+ qRegisterMetaType<QContactRelationshipSaveRequest*>("QContactRelationshipSaveRequest*");
+ QThreadSignalSpy spy(&rsr, SIGNAL(stateChanged(QContactAbstractRequest::State)));
+ rsr.setRelationship(testRel);
+ QCOMPARE(rsr.relationships(), saveList);
+ QVERIFY(!rsr.cancel()); // not started
+ QVERIFY(rsr.start());
+
+ QVERIFY((rsr.isActive() && rsr.state() == QContactAbstractRequest::ActiveState) || rsr.isFinished());
+ //QVERIFY(rsr.isFinished() || !rsr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(rsr.waitForFinished());
+ QVERIFY(rsr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ QList<QContactRelationship> expected = cm->relationships(QContactRelationship::HasSpouse, aId, QContactRelationship::First);
+ QList<QContactRelationship> result = rsr.relationships();
+ QCOMPARE(expected, result);
+ QVERIFY(result.contains(testRel));
+ QCOMPARE(cm->relationships(aId).size(), originalCount + 1); // should be one extra
+
+ // save a new relationship
+ testRel.setSecond(cId);
+ saveList.clear();
+ saveList << testRel;
+ rsr.setRelationships(saveList);
+ QCOMPARE(rsr.relationships(), saveList);
+ QVERIFY(!rsr.cancel()); // not started
+ QVERIFY(rsr.start());
+
+ QVERIFY((rsr.isActive() && rsr.state() == QContactAbstractRequest::ActiveState) || rsr.isFinished());
+ //QVERIFY(rsr.isFinished() || !rsr.start()); // already started. // thread scheduling means this is untestable
+ QVERIFY(rsr.waitForFinished());
+ QVERIFY(rsr.isFinished());
+ QVERIFY(spy.count() >= 1); // active + finished progress signals
+ spy.clear();
+
+ expected.clear();
+ expected = cm->relationships(QContactRelationship::HasSpouse, aId, QContactRelationship::First);
+ result = rsr.relationships();
+ QCOMPARE(result, QList<QContactRelationship>() << testRel);
+ QVERIFY(expected.contains(testRel));
+ QCOMPARE(cm->relationships(aId).size(), originalCount + 2); // should now be two extra
+
+ // cancelling
+ testRel.setSecond(aId); // shouldn't get saved (circular anyway)
+ saveList.clear();
+ saveList << testRel;
+ rsr.setRelationships(saveList);
+ int bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT; // attempt to cancel 40 times. If it doesn't work due to threading, bail out.
+ while (true) {
+ QVERIFY(!rsr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(rsr.start());
+ if (!rsr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ rsr.waitForFinished();
+ saveList.clear();
+ saveList << testRel;
+ rsr.setRelationships(saveList);
+ cm->removeRelationship(testRel); // probably shouldn't have been saved anyway (circular)
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+
+ // if we get here, then we are cancelling the request.
+ QVERIFY(rsr.waitForFinished());
+ QVERIFY(rsr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ // verify that the changes were not saved
+ QList<QContactRelationship> aRels = cm->relationships(aId, QContactRelationship::First);
+ QVERIFY(!aRels.contains(testRel));
+ QCOMPARE(cm->relationships(aId).size(), originalCount + 2); // should still only be two extra
+
+ break;
+ }
+
+ // restart, and wait for progress after cancel.
+ while (true) {
+ QVERIFY(!rsr.cancel()); // not started
+ FILL_QUEUE_WITH_FETCH_REQUESTS();
+ QVERIFY(rsr.start());
+ if (!rsr.cancel()) {
+ // due to thread scheduling, async cancel might be attempted
+ // after the request has already finished.. so loop and try again.
+ rsr.waitForFinished();
+ saveList.clear();
+ saveList << testRel;
+ rsr.setRelationships(saveList);
+ cm->removeRelationship(testRel); // probably shouldn't have been saved anyway (circular)
+ bailoutCount -= 1;
+ if (!bailoutCount) {
+// qWarning("Unable to test cancelling due to thread scheduling!");
+ bailoutCount = MAX_OPTIMISTIC_SCHEDULING_LIMIT;
+ break;
+ }
+ spy.clear();
+ continue;
+ }
+ rsr.waitForFinished();
+ QVERIFY(rsr.isCanceled());
+ QVERIFY(spy.count() >= 1); // active + cancelled progress signals
+ spy.clear();
+
+ // verify that the changes were not saved
+ QList<QContactRelationship> aRels = cm->relationships(aId, QContactRelationship::First);
+ QVERIFY(!aRels.contains(testRel));
+ QCOMPARE(cm->relationships(aId).size(), originalCount + 2); // should still only be two extra
+
+ break;
+ }
+}
+
+void tst_QContactAsync::maliciousManager()
+{
+ // use the invalid manager: passes all requests through to base class
+ QContactManager cm("invalid");
+ QContactFilter fil; // matches everything
+ QContactFetchRequest cfr;
+ cfr.setFilter(fil);
+ cfr.setManager(&cm);
+ QVERIFY(!cfr.start());
+ QVERIFY(!cfr.cancel());
+ QVERIFY(!cfr.waitForFinished());
+ QVERIFY(!cfr.start());
+
+ // ensure that the base class implementation of requestDestroyed() is called
+ QContactFetchRequest *destroyedRequest = new QContactFetchRequest;
+ destroyedRequest->setManager(&cm);
+ destroyedRequest->setFilter(fil);
+ QVERIFY(!destroyedRequest->start());
+ delete destroyedRequest;
+
+ // now use a malicious manager that deliberately calls
+ // things in a different thread
+ QContactManager mcm("maliciousplugin");
+ QCOMPARE(mcm.managerName(), QString("maliciousplugin"));
+ QList<QContact> emptyCList;
+ QList<QContactLocalId> emptyIList;
+ QList<QContactDetailDefinition> emptyDList;
+ QStringList emptyDNList;
+ QMap<QString, QContactDetailDefinition> emptyDMap;
+ cfr.setFilter(fil);
+ cfr.setManager(&mcm);
+ QVERIFY(cfr.start());
+
+ QContactLocalIdFetchRequest cifr;
+ cifr.setFilter(fil);
+ cifr.setManager(&mcm);
+ QVERIFY(cifr.start());
+
+ QContactRemoveRequest crr;
+ crr.setContactIds(mcm.contactIds(fil));
+ crr.setManager(&mcm);
+ QVERIFY(crr.start());
+
+ QContactSaveRequest csr;
+ csr.setContacts(emptyCList);
+ csr.setManager(&mcm);
+ QVERIFY(csr.start());
+
+ {
+ QContactDetailDefinitionFetchRequest dfr;
+ dfr.setDefinitionNames(emptyDNList);
+ dfr.setManager(&mcm);
+ QVERIFY(dfr.start());
+ }
+
+ {
+ QContactDetailDefinitionFetchRequest dfr;
+ dfr.setDefinitionNames(emptyDNList);
+ dfr.setManager(&mcm);
+ }
+
+ QContactDetailDefinitionSaveRequest dsr;
+ dsr.setDefinitions(emptyDList);
+ dsr.setManager(&mcm);
+ QVERIFY(dsr.start());
+
+ QContactDetailDefinitionRemoveRequest drr;
+ drr.setDefinitionNames(emptyDNList);
+ drr.setManager(&mcm);
+ QVERIFY(drr.start());
+}
+
+void tst_QContactAsync::testQuickDestruction()
+{
+ QFETCH(QString, uri);
+
+ // in this test, we create a manager, fire off a request, and delete the manager, all in quick succession
+ // this is to test for segfaults etc.
+ for (int i = 0; i < 10; i++) {
+ QContactFetchRequest cfr;
+ QContactManager *cm = prepareModel(uri);
+ cfr.setManager(cm);
+ cfr.start();
+ delete cm;
+ }
+ // in this test, we create a manager, fire off a request, delete the request, then delete the manager, all in quick succession
+ // this is to test for segfaults, etc.
+ for (int i = 0; i < 10; i++) {
+ QContactFetchRequest *cfr = new QContactFetchRequest;
+ QContactManager *cm = prepareModel(uri);
+ cfr->setManager(cm);
+ cfr->start();
+ delete cfr;
+ delete cm;
+ }
+ // in this test, we create a manager, fire off a request, delete the manager, then delete the request, all in quick succession
+ // this is to test for segfaults, etc.
+ for (int i = 0; i < 10; i++) {
+ QContactFetchRequest *cfr = new QContactFetchRequest;
+ QContactManager *cm = prepareModel(uri);
+ cfr->setManager(cm);
+ cfr->start();
+ delete cm;
+ delete cfr;
+ }
+ // in this test, we create a manager, fire off a request, and delete the request, all in quick succession
+ // this is to test for segfaults, etc.
+ QContactManager *cm = prepareModel(uri);
+ for (int i = 0; i < 10; i++) {
+ QContactFetchRequest *cfr = new QContactFetchRequest;
+ cfr->setManager(cm);
+ cfr->start();
+ delete cfr;
+ }
+ delete cm;
+}
+
+void tst_QContactAsync::threadDelivery()
+{
+ QFETCH(QString, uri);
+ QScopedPointer<QContactManager> cm(prepareModel(uri));
+ m_mainThreadId = cm->thread()->currentThreadId();
+ m_resultsAvailableSlotThreadId = m_mainThreadId;
+
+ // now perform a fetch request and check that the progress is delivered to the correct thread.
+ QContactFetchRequest *req = new QContactFetchRequest;
+ req->setManager(cm.data());
+ connect(req, SIGNAL(resultsAvailable()), this, SLOT(resultsAvailableReceived()));
+ req->start();
+
+ int totalWaitTime = 0;
+ QTest::qWait(1); // force it to process events at least once.
+ while (req->state() != QContactAbstractRequest::FinishedState) {
+ // ensure that the progress signal was delivered to the main thread.
+ QCOMPARE(m_mainThreadId, m_resultsAvailableSlotThreadId);
+
+ QTest::qWait(5); // spin until done
+ totalWaitTime += 5;
+
+ // break after 30 seconds.
+ if (totalWaitTime > 30000) {
+ delete req;
+ QSKIP("Asynchronous request not complete after 30 seconds!", SkipSingle);
+ }
+ }
+
+ // ensure that the progress signal was delivered to the main thread.
+ QCOMPARE(m_mainThreadId, m_resultsAvailableSlotThreadId);
+ delete req;
+}
+
+void tst_QContactAsync::resultsAvailableReceived()
+{
+ QContactFetchRequest *req = qobject_cast<QContactFetchRequest *>(QObject::sender());
+ if (req)
+ m_resultsAvailableSlotThreadId = req->thread()->currentThreadId();
+ else
+ qWarning() << "resultsAvailableReceived() : request deleted; unable to set thread id!";
+}
+
+void tst_QContactAsync::addManagers(QStringList stringlist)
+{
+ QTest::addColumn<QString>("uri");
+
+ // retrieve the list of available managers
+ QStringList managers = QContactManager::availableManagers();
+
+ // remove ones that we know will not pass
+ if (!stringlist.contains("invalid"))
+ managers.removeAll("invalid");
+ if (!stringlist.contains("maliciousplugin"))
+ managers.removeAll("maliciousplugin");
+ if (!stringlist.contains("testdummy"))
+ managers.removeAll("testdummy");
+ if (!stringlist.contains("symbiansim"))
+ managers.removeAll("symbiansim"); // SIM backend does not support all the required details for tests to pass.
+
+ foreach(QString mgr, managers) {
+ QMap<QString, QString> params;
+ QTest::newRow(QString("mgr='%1'").arg(mgr).toLatin1().constData()) << QContactManager::buildUri(mgr, params);
+ if (mgr == "memory") {
+ params.insert("id", "tst_QContactManager");
+ QTest::newRow(QString("mgr='%1', params").arg(mgr).toLatin1().constData()) << QContactManager::buildUri(mgr, params);
+ }
+ }
+}
+
+QContactManager* tst_QContactAsync::prepareModel(const QString& managerUri)
+{
+ QContactManager* cm = QContactManager::fromUri(managerUri);
+
+ // XXX TODO: ensure that this is the case:
+ // there should be no contacts in the database.
+ QList<QContactLocalId> toRemove = cm->contactIds();
+ foreach (const QContactLocalId& removeId, toRemove)
+ cm->removeContact(removeId);
+
+ QContact a, b, c;
+ QContactName aNameDetail;
+ aNameDetail.setFirstName("Aaron");
+ aNameDetail.setLastName("Aaronson");
+ a.saveDetail(&aNameDetail);
+ QContactName bNameDetail;
+ bNameDetail.setFirstName("Bob");
+ bNameDetail.setLastName("Aaronsen");
+ b.saveDetail(&bNameDetail);
+ QContactName cNameDetail;
+ cNameDetail.setFirstName("Borris");
+ cNameDetail.setLastName("Aaronsun");
+ c.saveDetail(&cNameDetail);
+
+ QContactPhoneNumber phn;
+ phn.setNumber("0123");
+ c.saveDetail(&phn);
+ phn.setNumber("3456");
+ b.saveDetail(&phn);
+ phn.setNumber("6789");
+ a.saveDetail(&phn);
+
+ QContactUrl url;
+ url.setUrl("http://test.nokia.com");
+ a.saveDetail(&url);
+
+ cm->saveContact(&a);
+ cm->saveContact(&b);
+ cm->saveContact(&c);
+
+ if (!cm->hasFeature(QContactManager::Relationships)) {
+ return cm;
+ }
+
+ if (cm->managerName() == "symbian") {
+ // Symbian backend does not support other relationships than HasMember (which is same as groups)
+ return cm;
+ }
+
+ QContactRelationship arb;
+ arb.setFirst(a.id());
+ arb.setSecond(b.id());
+ arb.setRelationshipType(QContactRelationship::HasManager);
+ cm->saveRelationship(&arb);
+
+ QContactRelationship brc;
+ brc.setFirst(b.id());
+ brc.setSecond(c.id());
+ brc.setRelationshipType(QContactRelationship::HasAssistant);
+ cm->saveRelationship(&brc);
+
+ QContactRelationship cra;
+ cra.setFirst(c.id());
+ cra.setSecond(a.id());
+ cra.setRelationshipType(QContactRelationship::HasSpouse);
+ cm->saveRelationship(&cra);
+
+ QContactRelationship arc;
+ arc.setFirst(a.id());
+ arc.setSecond(c.id());
+ arc.setRelationshipType(QContactRelationship::HasAssistant);
+ cm->saveRelationship(&arc);
+
+ QContactRelationship crb;
+ crb.setFirst(c.id());
+ crb.setSecond(b.id());
+ crb.setRelationshipType(QContactRelationship::IsSameAs);
+ cm->saveRelationship(&crb);
+
+ return cm;
+
+ // TODO: cleanup once test is complete
+}
+
+QTEST_MAIN(tst_QContactAsync)
+#include "tst_qcontactasync.moc"