diff --git a/akonadi/monitor_p.cpp b/akonadi/monitor_p.cpp index 94ff902d5..78845055c 100644 --- a/akonadi/monitor_p.cpp +++ b/akonadi/monitor_p.cpp @@ -1,318 +1,332 @@ /* Copyright (c) 2007 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // @cond PRIVATE #include "monitor_p.h" #include "collectionfetchjob.h" #include "collectionstatistics.h" #include "itemfetchjob.h" #include "notificationmessage_p.h" #include "session.h" #include using namespace Akonadi; static const int PipelineSize = 5; MonitorPrivate::MonitorPrivate(Monitor * parent) : q_ptr( parent ), nm( 0 ), monitorAll( false ), collectionCache( 3*PipelineSize ), // needs to be at least 3x pipeline size for the collection move case itemCache( PipelineSize ), // needs to be at least 1x pipeline size fetchCollection( false ), fetchCollectionStatistics( false ) { } void MonitorPrivate::init() { QObject::connect( &collectionCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable()) ); QObject::connect( &itemCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable()) ); } bool MonitorPrivate::connectToNotificationManager() { NotificationMessage::registerDBusTypes(); if ( !nm ) nm = new org::freedesktop::Akonadi::NotificationManager( QLatin1String( "org.freedesktop.Akonadi" ), QLatin1String( "/notifications" ), QDBusConnection::sessionBus(), q_ptr ); else return true; if ( !nm ) { kWarning() << "Unable to connect to notification manager"; } else { QObject::connect( nm, SIGNAL(notify(Akonadi::NotificationMessage::List)), q_ptr, SLOT(slotNotify(Akonadi::NotificationMessage::List)) ); return true; } return false; } int MonitorPrivate::pipelineSize() const { return PipelineSize; } bool MonitorPrivate::acceptNotification(const NotificationMessage & msg) { if ( isSessionIgnored( msg.sessionId() ) ) return false; switch ( msg.type() ) { case NotificationMessage::InvalidType: kWarning() << "Received invalid change notification!"; return false; case NotificationMessage::Item: return isItemMonitored( msg.uid(), msg.parentCollection(), msg.parentDestCollection(), msg.mimeType(), msg.resource() ) || isCollectionMonitored( msg.parentCollection(), msg.resource() ) || isCollectionMonitored( msg.parentDestCollection(), msg.resource() ); case NotificationMessage::Collection: return isCollectionMonitored( msg.uid(), msg.resource() ) || isCollectionMonitored( msg.parentCollection(), msg.resource() ) || isCollectionMonitored( msg.parentDestCollection(), msg.resource() ); } Q_ASSERT( false ); return false; } void MonitorPrivate::dispatchNotifications() { while ( pipeline.size() < pipelineSize() && !pendingNotifications.isEmpty() ) { const NotificationMessage msg = pendingNotifications.dequeue(); if ( ensureDataAvailable( msg ) && pipeline.isEmpty() ) emitNotification( msg ); else pipeline.enqueue( msg ); } } bool MonitorPrivate::ensureDataAvailable( const NotificationMessage &msg ) { bool allCached = true; if ( fetchCollection ) { if ( !collectionCache.ensureCached( msg.parentCollection(), mCollectionFetchScope ) ) allCached = false; if ( msg.operation() == NotificationMessage::Move && !collectionCache.ensureCached( msg.parentDestCollection(), mCollectionFetchScope ) ) allCached = false; } if ( msg.operation() == NotificationMessage::Remove ) return allCached; // the actual object is gone already, nothing to fetch there if ( msg.type() == NotificationMessage::Item && !mItemFetchScope.isEmpty() ) { if ( !itemCache.ensureCached( msg.uid(), mItemFetchScope ) ) allCached = false; } else if ( msg.type() == NotificationMessage::Collection && fetchCollection ) { if ( !collectionCache.ensureCached( msg.uid(), mCollectionFetchScope ) ) allCached = false; } return allCached; } void MonitorPrivate::emitNotification( const NotificationMessage &msg ) { const Collection parent = collectionCache.retrieve( msg.parentCollection() ); Collection destParent; if ( msg.operation() == NotificationMessage::Move ) destParent = collectionCache.retrieve( msg.parentDestCollection() ); if ( msg.type() == NotificationMessage::Collection ) { const Collection col = collectionCache.retrieve( msg.uid() ); emitCollectionNotification( msg, col, parent, destParent ); } else if ( msg.type() == NotificationMessage::Item ) { const Item item = itemCache.retrieve( msg.uid() ); emitItemNotification( msg, item, parent, destParent ); } } void MonitorPrivate::dataAvailable() { while ( !pipeline.isEmpty() ) { const NotificationMessage msg = pipeline.head(); if ( ensureDataAvailable( msg ) ) { emitNotification( msg ); pipeline.dequeue(); } else { break; } } dispatchNotifications(); } void MonitorPrivate::updatePendingStatistics( const NotificationMessage &msg ) { if ( msg.type() == NotificationMessage::Item ) { notifyCollectionStatisticsWatchers( msg.parentCollection(), msg.resource() ); } else if ( msg.type() == NotificationMessage::Collection && msg.operation() == NotificationMessage::Remove ) { // no need for statistics updates anymore recentlyChangedCollections.remove( msg.uid() ); } } void MonitorPrivate::slotSessionDestroyed( QObject * object ) { Session* session = qobject_cast( object ); if ( session ) sessions.removeAll( session->sessionId() ); } void MonitorPrivate::slotStatisticsChangedFinished( KJob* job ) { if ( job->error() ) { kWarning() << "Error on fetching collection statistics: " << job->errorText(); } else { CollectionStatisticsJob *statisticsJob = static_cast( job ); emit q_ptr->collectionStatisticsChanged( statisticsJob->collection().id(), statisticsJob->statistics() ); } } void MonitorPrivate::slotFlushRecentlyChangedCollections() { foreach( Collection::Id collection, recentlyChangedCollections ) { if ( fetchCollectionStatistics ) { fetchStatistics( collection ); } else { static const CollectionStatistics dummyStatistics; emit q_ptr->collectionStatisticsChanged( collection, dummyStatistics ); } } recentlyChangedCollections.clear(); } void MonitorPrivate::slotNotify( const NotificationMessage::List &msgs ) { foreach ( const NotificationMessage &msg, msgs ) { invalidateCaches( msg ); if ( acceptNotification( msg ) ) { updatePendingStatistics( msg ); NotificationMessage::appendAndCompress( pendingNotifications, msg ); } } dispatchNotifications(); } void MonitorPrivate::emitItemNotification( const NotificationMessage &msg, const Item &item, const Collection &collection, const Collection &collectionDest ) { Q_ASSERT( msg.type() == NotificationMessage::Item ); Collection col = collection; Collection colDest = collectionDest; if ( !col.isValid() ) { col = Collection( msg.parentCollection() ); col.setResource( QString::fromUtf8( msg.resource() ) ); } if ( !colDest.isValid() ) { colDest = Collection( msg.parentDestCollection() ); // FIXME setResource here required ? } Item it = item; if ( !it.isValid() ) { it = Item( msg.uid() ); it.setRemoteId( msg.remoteId() ); it.setMimeType( msg.mimeType() ); } + if ( !it.parentCollection().isValid() ) { + if ( msg.operation() == NotificationMessage::Move ) + it.setParentCollection( colDest ); + else + it.setParentCollection( col ); + } switch ( msg.operation() ) { case NotificationMessage::Add: emit q_ptr->itemAdded( it, col ); break; case NotificationMessage::Modify: emit q_ptr->itemChanged( it, msg.itemParts() ); break; case NotificationMessage::Move: emit q_ptr->itemMoved( it, col, colDest ); break; case NotificationMessage::Remove: emit q_ptr->itemRemoved( it ); break; case NotificationMessage::Link: emit q_ptr->itemLinked( it, col ); break; case NotificationMessage::Unlink: emit q_ptr->itemUnlinked( it, col ); break; default: kDebug() << "Unknown operation type" << msg.operation() << "in item change notification"; break; } } void MonitorPrivate::emitCollectionNotification( const NotificationMessage &msg, const Collection &col, const Collection &par, const Collection &dest ) { Q_ASSERT( msg.type() == NotificationMessage::Collection ); Collection collection = col; if ( !collection.isValid() ) { collection = Collection( msg.uid() ); - collection.setParentCollection( Collection( msg.parentCollection() ) ); collection.setResource( QString::fromUtf8( msg.resource() ) ); collection.setRemoteId( msg.remoteId() ); } + Collection parent = par; if ( !parent.isValid() ) parent = Collection( msg.parentCollection() ); Collection destination = dest; if ( !destination.isValid() ) destination = Collection( msg.parentDestCollection() ); + + if ( !collection.parentCollection().isValid() ) { + if ( msg.operation() == NotificationMessage::Move ) + collection.setParentCollection( destination ); + else + collection.setParentCollection( parent ); + } + switch ( msg.operation() ) { case NotificationMessage::Add: emit q_ptr->collectionAdded( collection, parent ); break; case NotificationMessage::Modify: emit q_ptr->collectionChanged( collection ); break; case NotificationMessage::Move: emit q_ptr->collectionMoved( collection, parent, destination ); break; case NotificationMessage::Remove: emit q_ptr->collectionRemoved( collection ); break; default: kDebug() << "Unknown operation type" << msg.operation() << "in collection change notification"; } } void MonitorPrivate::invalidateCaches( const NotificationMessage &msg ) { // remove invalidates if ( msg.operation() == NotificationMessage::Remove ) { if ( msg.type() == NotificationMessage::Collection ) { collectionCache.invalidate( msg.uid() ); } else if ( msg.type() == NotificationMessage::Item ) { itemCache.invalidate( msg.uid() ); } } // modify removes the cache entry, as we need to re-fetch if ( msg.operation() == NotificationMessage::Modify ) { if ( msg.type() == NotificationMessage::Collection ) { collectionCache.update( msg.uid(), mCollectionFetchScope ); } else if ( msg.type() == NotificationMessage::Item ) { itemCache.update( msg.uid(), mItemFetchScope ); } } } // @endcond diff --git a/akonadi/tests/monitortest.cpp b/akonadi/tests/monitortest.cpp index 182b2f9cf..68033e3d0 100644 --- a/akonadi/tests/monitortest.cpp +++ b/akonadi/tests/monitortest.cpp @@ -1,359 +1,362 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "monitortest.h" #include "test_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; QTEST_AKONADIMAIN( MonitorTest, NoGUI ) static Collection res3; Q_DECLARE_METATYPE(Akonadi::Collection::Id) Q_DECLARE_METATYPE(QSet) void MonitorTest::initTestCase() { Control::start(); res3 = Collection( collectionIdFromPath( "res3" ) ); // switch all resources offline to reduce interference from them foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) agent.setIsOnline( false ); } void MonitorTest::testMonitor_data() { QTest::addColumn( "fetchCol" ); QTest::newRow( "with collection fetching" ) << true; QTest::newRow( "without collection fetching" ) << false; } void MonitorTest::testMonitor() { QFETCH( bool, fetchCol ); Monitor *monitor = new Monitor( this ); monitor->setCollectionMonitored( Collection::root() ); monitor->fetchCollection( fetchCol ); monitor->itemFetchScope().fetchFullPayload(); // monitor signals qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType >(); QSignalSpy caddspy( monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)) ); QSignalSpy cmodspy( monitor, SIGNAL(collectionChanged(const Akonadi::Collection&)) ); QSignalSpy cmvspy( monitor, SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)) ); QSignalSpy crmspy( monitor, SIGNAL(collectionRemoved(const Akonadi::Collection&)) ); QSignalSpy cstatspy( monitor, SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)) ); QSignalSpy iaddspy( monitor, SIGNAL(itemAdded(const Akonadi::Item&, const Akonadi::Collection&)) ); QSignalSpy imodspy( monitor, SIGNAL(itemChanged(const Akonadi::Item&, const QSet&)) ); QSignalSpy imvspy( monitor, SIGNAL(itemMoved(const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection&)) ); QSignalSpy irmspy( monitor, SIGNAL(itemRemoved(const Akonadi::Item&)) ); QVERIFY( caddspy.isValid() ); QVERIFY( cmodspy.isValid() ); QVERIFY( cmvspy.isValid() ); QVERIFY( crmspy.isValid() ); QVERIFY( cstatspy.isValid() ); QVERIFY( iaddspy.isValid() ); QVERIFY( imodspy.isValid() ); QVERIFY( imvspy.isValid() ); QVERIFY( irmspy.isValid() ); // create a collection Collection monitorCol; monitorCol.setParentCollection( res3 ); monitorCol.setName( "monitor" ); CollectionCreateJob *create = new CollectionCreateJob( monitorCol, this ); - QVERIFY( create->exec() ); + AKVERIFYEXEC( create ); monitorCol = create->collection(); QVERIFY( monitorCol.isValid() ); QTest::qWait(1000); // make sure the DBus signal has been processed QCOMPARE( caddspy.count(), 1 ); QList arg = caddspy.takeFirst(); Collection col = arg.at(0).value(); QCOMPARE( col, monitorCol ); if ( fetchCol ) QCOMPARE( col.name(), QString("monitor") ); Collection parent = arg.at(1).value(); QCOMPARE( parent, res3 ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // add an item Item newItem; newItem.setMimeType( "application/octet-stream" ); ItemCreateJob *append = new ItemCreateJob( newItem, monitorCol, this ); QVERIFY( append->exec() ); Item monitorRef = append->item(); QVERIFY( monitorRef.isValid() ); QTest::qWait(1000); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); /* qRegisterMetaType() registers the type with a name of "qlonglong". Doing qRegisterMetaType( "Akonadi::Collection::Id" ) doesn't help. The problem here is that Akonadi::Collection::Id is a typedef to qlonglong, and qlonglong is already a registered meta type. So the signal spy will give us a QVariant of type Akonadi::Collection::Id, but calling .value() on that variant will in fact end up calling qvariant_cast. From the point of view of QMetaType, Akonadi::Collection::Id and qlonglong are different types, so QVariant can't convert, and returns a default-constructed qlonglong, zero. When connecting to a real slot (without QSignalSpy), this problem is avoided, because the casting is done differently (via a lot of void pointers). The docs say nothing about qRegisterMetaType -ing a typedef, so I'm not sure if this is a bug or not. (cberzan) */ QCOMPARE( iaddspy.count(), 1 ); arg = iaddspy.takeFirst(); Item item = arg.at( 0 ).value(); QCOMPARE( item, monitorRef ); QCOMPARE( item.mimeType(), QString::fromLatin1( "application/octet-stream" ) ); Collection collection = arg.at( 1 ).value(); QCOMPARE( collection.id(), monitorCol.id() ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // modify an item item.setPayload( "some new content" ); ItemModifyJob *store = new ItemModifyJob( item, this ); QVERIFY( store->exec() ); QTest::qWait(1000); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); QCOMPARE( imodspy.count(), 1 ); arg = imodspy.takeFirst(); item = arg.at( 0 ).value(); QCOMPARE( monitorRef, item ); QVERIFY( item.hasPayload() ); QCOMPARE( item.payload(), QByteArray( "some new content" ) ); QSet parts = arg.at( 1 ).value >(); QCOMPARE( parts, QSet() << "PLD:RFC822" ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // move an item ItemMoveJob *move = new ItemMoveJob( item, res3 ); QVERIFY( move->exec() ); QTest::qWait( 1000 ); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); QCOMPARE( imvspy.count(), 1 ); arg = imvspy.takeFirst(); item = arg.at( 0 ).value(); // the item QCOMPARE( monitorRef, item ); col = arg.at( 1 ).value(); // the source collection QCOMPARE( col.id(), monitorCol.id() ); col = arg.at( 2 ).value(); // the destination collection QCOMPARE( col.id(), res3.id() ); QCOMPARE( cmodspy.count(), 2 ); arg = cmodspy.takeFirst(); Collection col1 = arg.at( 0 ).value(); arg = cmodspy.takeFirst(); Collection col2 = arg.at( 0 ).value(); // source and dest collections, in any order QVERIFY( ( col1.id() == monitorCol.id() && col2.id() == res3.id() ) || ( col2.id() == monitorCol.id() && col1.id() == res3.id() ) ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // delete an item ItemDeleteJob *del = new ItemDeleteJob( monitorRef, this ); QVERIFY( del->exec() ); QTest::qWait(1000); QCOMPARE( cstatspy.count(), 1 ); arg = cstatspy.takeFirst(); QEXPECT_FAIL( "", "Don't know how to handle 'Akonadi::Collection::Id', use qRegisterMetaType to register it. <-- I did this, but it still doesn't work!", Continue ); QCOMPARE( arg.at(0).value(), monitorCol.id() ); cmodspy.clear(); QCOMPARE( irmspy.count(), 1 ); arg = irmspy.takeFirst(); Item ref = qvariant_cast( arg.at(0) ); QCOMPARE( monitorRef, ref ); + QCOMPARE( ref.parentCollection(), res3 ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); imvspy.clear(); // modify a collection monitorCol.setName( "changed name" ); CollectionModifyJob *mod = new CollectionModifyJob( monitorCol, this ); AKVERIFYEXEC( mod ); QTest::qWait(1000); QCOMPARE( cmodspy.count(), 1 ); arg = cmodspy.takeFirst(); col = arg.at(0).value(); QCOMPARE( col, monitorCol ); if ( fetchCol ) QCOMPARE( col.name(), QString("changed name") ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // move a collection Collection dest = Collection( collectionIdFromPath( "res1/foo" ) ); CollectionMoveJob *cmove = new CollectionMoveJob( monitorCol, dest, this ); AKVERIFYEXEC( cmove ); QTest::qWait( 1000 ); QCOMPARE( cmvspy.count(), 1 ); arg = cmvspy.takeFirst(); col = arg.at( 0 ).value(); QCOMPARE( col, monitorCol ); if ( fetchCol ) QCOMPARE( col.name(), monitorCol.name() ); col = arg.at( 1 ).value(); QCOMPARE( col, res3 ); col = arg.at( 2 ).value(); QCOMPARE( col, dest ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( crmspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); // delete a collection CollectionDeleteJob *cdel = new CollectionDeleteJob( monitorCol, this ); QVERIFY( cdel->exec() ); QTest::qWait(1000); QCOMPARE( crmspy.count(), 1 ); arg = crmspy.takeFirst(); - QCOMPARE( arg.at(0).value().id(), monitorCol.id() ); + col = arg.at(0).value(); + QCOMPARE( col.id(), monitorCol.id() ); + QCOMPARE( col.parentCollection(), dest ); QVERIFY( caddspy.isEmpty() ); QVERIFY( cmodspy.isEmpty() ); QVERIFY( cmvspy.isEmpty() ); QVERIFY( cstatspy.isEmpty() ); QVERIFY( iaddspy.isEmpty() ); QVERIFY( imodspy.isEmpty() ); QVERIFY( imvspy.isEmpty() ); QVERIFY( irmspy.isEmpty() ); } void MonitorTest::testVirtualCollectionsMonitoring() { Monitor *monitor = new Monitor( this ); monitor->setCollectionMonitored( Collection( 1 ) ); // top-level 'Search' collection QSignalSpy caddspy( monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)) ); QVERIFY( caddspy.isValid() ); SearchCreateJob *job = new SearchCreateJob( "Test search collection", "test-search-query", this ); AKVERIFYEXEC( job ); QTest::qWait( 1000 ); QCOMPARE( caddspy.count(), 1 ); } #include "monitortest.moc"