diff --git a/kdecore/tests/kservicetest.cpp b/kdecore/tests/kservicetest.cpp index 747e11ad67..24fbbae674 100644 --- a/kdecore/tests/kservicetest.cpp +++ b/kdecore/tests/kservicetest.cpp @@ -1,583 +1,583 @@ /* * Copyright (C) 2006 David Faure * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation; * * 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 #include "kservicetest.h" #include "kservicetest.moc" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QTEST_KDEMAIN_CORE( KServiceTest ) static void eraseProfiles() { QString profilerc = KStandardDirs::locateLocal( "config", "profilerc" ); if ( !profilerc.isEmpty() ) QFile::remove( profilerc ); profilerc = KStandardDirs::locateLocal( "config", "servicetype_profilerc" ); if ( !profilerc.isEmpty() ) QFile::remove( profilerc ); } void KServiceTest::initTestCase() { // A non-C locale is necessary for some tests. // This locale must have the following properties: // - some character other than dot as decimal separator // If it cannot be set, locale-dependent tests are skipped. setlocale(LC_ALL, "fr_FR.utf8"); m_hasNonCLocale = (setlocale(LC_ALL, NULL) == QByteArray("fr_FR.utf8")); if (!m_hasNonCLocale) { kDebug() << "Setting locale to fr_FR.utf8 failed"; } m_hasKde4Konsole = false; eraseProfiles(); // Create some fake services for the tests below, and ensure they are in ksycoca. // fakeservice: deleted and recreated by testKSycocaUpdate, don't use in other tests bool mustUpdateKSycoca = !KService::serviceByDesktopPath("fakeservice.desktop"); const QString fakeService = KStandardDirs::locateLocal("services", "fakeservice.desktop"); if (!QFile::exists(fakeService)) { mustUpdateKSycoca = true; createFakeService(); } // fakepart: a readwrite part, like katepart if (!KService::serviceByDesktopPath("fakepart.desktop")) { mustUpdateKSycoca = true; } const QString fakePart = KStandardDirs::locateLocal("services", "fakepart.desktop"); if (!QFile::exists(fakePart)) { mustUpdateKSycoca = true; KDesktopFile file(fakePart); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "FakePart"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-Library", "fakepart"); group.writeEntry("X-KDE-Protocols", "http,ftp"); group.writeEntry("X-KDE-ServiceTypes", "KParts/ReadOnlyPart,Browser/View,KParts/ReadWritePart"); group.writeEntry("MimeType", "text/plain;"); } // faketextplugin: a ktexteditor plugin if (!KService::serviceByDesktopPath("faketextplugin.desktop")) { mustUpdateKSycoca = true; } const QString fakeTextplugin = KStandardDirs::locateLocal("services", "faketextplugin.desktop"); if (!QFile::exists(fakeTextplugin)) { mustUpdateKSycoca = true; KDesktopFile file(fakeTextplugin); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "FakeTextPlugin"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-Library", "faketextplugin"); group.writeEntry("X-KDE-ServiceTypes", "KTextEditor/Plugin"); group.writeEntry("MimeType", "text/plain;"); } if ( mustUpdateKSycoca ) { // Update ksycoca in ~/.kde-unit-test after creating the above QProcess::execute( KGlobal::dirs()->findExe(KBUILDSYCOCA_EXENAME), QStringList() << "--noincremental" ); kDebug() << "waiting for signal"; QVERIFY(QTest::kWaitForSignal(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), 10000)); kDebug() << "got signal"; } } void KServiceTest::cleanupTestCase() { // If I want the konqueror unit tests to work, then I better not have a non-working part // as the preferred part for text/plain... QStringList services; services << "fakeservice.desktop" << "fakepart.desktop" << "faketextplugin.desktop"; Q_FOREACH(const QString& service, services) { const QString fakeService = KStandardDirs::locateLocal("services", service); QFile::remove(fakeService); } //QProcess::execute( KGlobal::dirs()->findExe(KBUILDSYCOCA_EXENAME) ); KProcess proc; proc << KStandardDirs::findExe(KBUILDSYCOCA_EXENAME); proc.setOutputChannelMode(KProcess::MergedChannels); // silence kbuildsycoca output proc.execute(); } void KServiceTest::testByName() { if ( !KSycoca::isAvailable() ) QSKIP( "ksycoca not available", SkipAll ); KServiceType::Ptr s0 = KServiceType::serviceType("KParts/ReadOnlyPart"); QVERIFY( s0 ); QCOMPARE( s0->name(), QString::fromLatin1("KParts/ReadOnlyPart") ); KService::Ptr khtml = KService::serviceByDesktopPath("khtml.desktop"); QCOMPARE( khtml->name(), QString::fromLatin1("KHTML")); } void KServiceTest::testProperty() { KService::Ptr kdedkcookiejar = KService::serviceByDesktopPath("kded/kcookiejar.desktop"); QVERIFY(kdedkcookiejar); QCOMPARE(kdedkcookiejar->entryPath(), QString("kded/kcookiejar.desktop")); QCOMPARE(kdedkcookiejar->property("ServiceTypes").toStringList().join(","), QString("KDEDModule")); QCOMPARE(kdedkcookiejar->property("X-KDE-Kded-autoload").toBool(), false); QCOMPARE(kdedkcookiejar->property("X-KDE-Kded-load-on-demand").toBool(), true); QVERIFY(!kdedkcookiejar->property("Name").toString().isEmpty()); QVERIFY(!kdedkcookiejar->property("Name[fr]", QVariant::String).isValid()); KService::Ptr kjavaappletviewer = KService::serviceByDesktopPath("kjavaappletviewer.desktop"); QVERIFY(kjavaappletviewer); QCOMPARE(kjavaappletviewer->property("X-KDE-BrowserView-PluginsInfo").toString(), QString("kjava/pluginsinfo")); // Test property("X-KDE-Protocols"), which triggers the KServiceReadProperty code. KService::Ptr fakePart = KService::serviceByDesktopPath("fakepart.desktop"); QVERIFY(fakePart); // see initTestCase; it should be found. QVERIFY(fakePart->propertyNames().contains("X-KDE-Protocols")); const QStringList protocols = fakePart->property("X-KDE-Protocols").toStringList(); QCOMPARE(protocols, QStringList() << "http" << "ftp"); } void KServiceTest::testAllServiceTypes() { if ( !KSycoca::isAvailable() ) QSKIP( "ksycoca not available", SkipAll ); const KServiceType::List allServiceTypes = KServiceType::allServiceTypes(); // A bit of checking on the allServiceTypes list itself KServiceType::List::ConstIterator stit = allServiceTypes.begin(); const KServiceType::List::ConstIterator stend = allServiceTypes.end(); for ( ; stit != stend; ++stit ) { const KServiceType::Ptr servtype = (*stit); const QString name = servtype->name(); QVERIFY( !name.isEmpty() ); QVERIFY( servtype->sycocaType() == KST_KServiceType ); } } void KServiceTest::testAllServices() { if ( !KSycoca::isAvailable() ) QSKIP( "ksycoca not available", SkipAll ); const KService::List lst = KService::allServices(); QVERIFY( !lst.isEmpty() ); for ( KService::List::ConstIterator it = lst.begin(); it != lst.end(); ++it ) { const KService::Ptr service = (*it); QVERIFY( service->isType( KST_KService ) ); const QString name = service->name(); const QString entryPath = service->entryPath(); //kDebug() << name << "entryPath=" << entryPath << "menuId=" << service->menuId(); QVERIFY( !name.isEmpty() ); QVERIFY( !entryPath.isEmpty() ); KService::Ptr lookedupService = KService::serviceByDesktopPath( entryPath ); QVERIFY( lookedupService ); // not null QCOMPARE( lookedupService->entryPath(), entryPath ); if ( service->isApplication() ) { const QString menuId = service->menuId(); if ( menuId.isEmpty() ) qWarning( "%s has an empty menuId!", qPrintable( entryPath ) ); else if ( menuId == "kde4-konsole.desktop" ) m_hasKde4Konsole = true; QVERIFY( !menuId.isEmpty() ); lookedupService = KService::serviceByMenuId( menuId ); QVERIFY( lookedupService ); // not null QCOMPARE( lookedupService->menuId(), menuId ); } } } // Helper method for all the trader tests static bool offerListHasService( const KService::List& offers, const QString& entryPath ) { bool found = false; KService::List::const_iterator it = offers.begin(); for ( ; it != offers.end() ; it++ ) { if ( (*it)->entryPath() == entryPath ) { if( found ) { // should be there only once qWarning( "ERROR: %s was found twice in the list", qPrintable( entryPath ) ); return false; // make test fail } found = true; } } return found; } void KServiceTest::testDBUSStartupType() { if ( !KSycoca::isAvailable() ) QSKIP( "ksycoca not available", SkipAll ); if ( !m_hasKde4Konsole ) QSKIP( "kde4-konsole.desktop not available", SkipAll ); //KService::Ptr konsole = KService::serviceByMenuId( "kde4-konsole.desktop" ); KService::Ptr konsole = KService::serviceByDesktopName( "konsole" ); QVERIFY(konsole); QCOMPARE(konsole->menuId(), QString("kde4-konsole.desktop")); //qDebug() << konsole->entryPath(); QCOMPARE((int)konsole->dbusStartupType(), (int)KService::DBusUnique); } void KServiceTest::testByStorageId() { if ( !KSycoca::isAvailable() ) QSKIP("ksycoca not available", SkipAll); - if (!m_hasKde4Konsole) - QSKIP("kdebase not installed", SkipAll); - QVERIFY(KService::serviceByMenuId("kde4-systemsettings.desktop")); - QVERIFY(!KService::serviceByMenuId("kde4-systemsettings")); // doesn't work, extension mandatory - QVERIFY(KService::serviceByStorageId("kde4-systemsettings.desktop")); - //QVERIFY(!KService::serviceByStorageId("kde4-systemsettings")); // doesn't work, extension mandatory; also shows a debug + QVERIFY(KService::serviceByMenuId("kde4-kmailservice.desktop")); + QVERIFY(!KService::serviceByMenuId("kde4-kmailservice")); // doesn't work, extension mandatory + QVERIFY(KService::serviceByStorageId("kde4-kmailservice.desktop")); + //QVERIFY(!KService::serviceByStorageId("kde4-kmailservice")); // doesn't work, extension mandatory; also shows a debug + // This one fails here; probably because there are two such files, so this would be too // ambiguous... According to the testAllServices output, the entryPaths are - // entryPath="/d/kde/inst/kde4/share/applications/kde4/systemsettings.desktop" - // entryPath= "/usr/share/applications/kde/systemsettings.desktop" + // entryPath="/d/kde/inst/kde4/share/applications/kde4/kmailservice.desktop" + // entryPath= "/usr/share/applications/kde4/kmailservice.desktop" // - //QVERIFY(KService::serviceByDesktopPath("systemsettings.desktop")); - QVERIFY(KService::serviceByDesktopName("systemsettings")); - // Fails here, finds the kde3 systemsettings from /usr/share - //QCOMPARE(KService::serviceByDesktopName("systemsettings")->menuId(), QString("kde4-systemsettings,desktop")); + //QVERIFY(KService::serviceByDesktopPath("kmailservice.desktop")); + + QVERIFY(KService::serviceByDesktopName("kmailservice")); + // This could fail if it finds the kde3 kmailservice from /usr/share. But who still has kde3 :-) + QCOMPARE(KService::serviceByDesktopName("kmailservice")->menuId(), QString("kde4-kmailservice.desktop")); } void KServiceTest::testServiceTypeTraderForReadOnlyPart() { if ( !KSycoca::isAvailable() ) QSKIP( "ksycoca not available", SkipAll ); // Querying trader for services associated with KParts/ReadOnlyPart KService::List offers = KServiceTypeTrader::self()->query("KParts/ReadOnlyPart"); QVERIFY( offers.count() > 0 ); //foreach( KService::Ptr service, offers ) // qDebug( "%s %s", qPrintable( service->name() ), qPrintable( service->entryPath() ) ); m_firstOffer = offers[0]->entryPath(); // Only test for parts provided by kdelibs, or better, by this unittest: QVERIFY( offerListHasService( offers, "fakepart.desktop" ) ); QVERIFY( offerListHasService( offers, "kmultipart.desktop" ) ); QVERIFY( offerListHasService( offers, "khtml.desktop" ) ); QVERIFY( offerListHasService( offers, "khtmlimage.desktop" ) ); QVERIFY( offerListHasService( offers, "kjavaappletviewer.desktop" ) ); // Check ordering according to InitialPreference int lastPreference = -1; bool lastAllowedAsDefault = true; Q_FOREACH(KService::Ptr service, offers) { const QString path = service->entryPath(); const int preference = service->initialPreference(); // ## might be wrong if we use per-servicetype preferences... //qDebug( "%s has preference %d, allowAsDefault=%d", qPrintable( path ), preference, service->allowAsDefault() ); if ( lastAllowedAsDefault && !service->allowAsDefault() ) { // first "not allowed as default" offer lastAllowedAsDefault = false; lastPreference = -1; // restart } if ( lastPreference != -1 ) QVERIFY( preference <= lastPreference ); lastPreference = preference; } // Now look for any KTextEditor/Plugin offers = KServiceTypeTrader::self()->query("KTextEditor/Plugin"); QVERIFY( offerListHasService( offers, "fakeservice.desktop" ) ); QVERIFY( offerListHasService( offers, "faketextplugin.desktop" ) ); } void KServiceTest::testTraderConstraints() { if ( !KSycoca::isAvailable() ) QSKIP( "ksycoca not available", SkipAll ); KService::List offers = KServiceTypeTrader::self()->query("KTextEditor/Plugin", "Library == 'faketextplugin'"); QCOMPARE(offers.count(), 1); QVERIFY( offerListHasService( offers, "faketextplugin.desktop" ) ); if (m_hasNonCLocale) { // Test float parsing, must use dot as decimal separator independent of locale. offers = KServiceTypeTrader::self()->query("KTextEditor/Plugin", "([X-KDE-Version] > 4.559) and ([X-KDE-Version] < 4.561)"); QCOMPARE(offers.count(), 1); QVERIFY(offerListHasService( offers, "fakeservice.desktop")); } // A test with an invalid query, to test for memleaks offers = KServiceTypeTrader::self()->query("KTextEditor/Plugin", "A == B OR C == D AND OR Foo == 'Parse Error'"); QVERIFY(offers.isEmpty()); } void KServiceTest::testHasServiceType1() // with services constructed with a full path (rare) { QString fakepartPath = KStandardDirs::locate( "services", "fakepart.desktop" ); QVERIFY( !fakepartPath.isEmpty() ); KService fakepart( fakepartPath ); QVERIFY( fakepart.hasServiceType( "KParts/ReadOnlyPart" ) ); QVERIFY( fakepart.hasServiceType( "KParts/ReadWritePart" ) ); QString faketextPluginPath = KStandardDirs::locate( "services", "faketextplugin.desktop" ); QVERIFY( !faketextPluginPath.isEmpty() ); KService faketextPlugin( faketextPluginPath ); QVERIFY( faketextPlugin.hasServiceType( "KTextEditor/Plugin" ) ); QVERIFY( !faketextPlugin.hasServiceType( "KParts/ReadOnlyPart" ) ); } void KServiceTest::testHasServiceType2() // with services coming from ksycoca { KService::Ptr fakepart = KService::serviceByDesktopPath( "fakepart.desktop" ); QVERIFY( !fakepart.isNull() ); QVERIFY( fakepart->hasServiceType( "KParts/ReadOnlyPart" ) ); QVERIFY( fakepart->hasServiceType( "KParts/ReadWritePart" ) ); KService::Ptr faketextPlugin = KService::serviceByDesktopPath( "faketextplugin.desktop" ); QVERIFY( !faketextPlugin.isNull() ); QVERIFY( faketextPlugin->hasServiceType( "KTextEditor/Plugin" ) ); QVERIFY( !faketextPlugin->hasServiceType( "KParts/ReadOnlyPart" ) ); } void KServiceTest::testWriteServiceTypeProfile() { const QString serviceType = "KParts/ReadOnlyPart"; KService::List services, disabledServices; services.append(KService::serviceByDesktopPath("khtmlimage.desktop")); services.append(KService::serviceByDesktopPath("fakepart.desktop")); disabledServices.append(KService::serviceByDesktopPath("khtml.desktop")); KService::List::ConstIterator servit = services.constBegin(); for( ; servit != services.constEnd(); ++servit) { QVERIFY(!servit->isNull()); } KServiceTypeProfile::writeServiceTypeProfile( serviceType, services, disabledServices ); // Check that the file got written QString profilerc = KStandardDirs::locateLocal( "config", "servicetype_profilerc" ); QVERIFY(!profilerc.isEmpty()); QVERIFY(QFile::exists(profilerc)); KService::List offers = KServiceTypeTrader::self()->query( serviceType ); QVERIFY( offers.count() > 0 ); // not empty //foreach( KService::Ptr service, offers ) // qDebug( "%s %s", qPrintable( service->name() ), qPrintable( service->entryPath() ) ); QVERIFY( offers.count() >= 3 ); // at least 3, even QCOMPARE( offers[0]->entryPath(), QString("khtmlimage.desktop") ); QCOMPARE( offers[1]->entryPath(), QString("fakepart.desktop") ); QVERIFY( offerListHasService( offers, "kmultipart.desktop" ) ); // should still be somewhere in there QVERIFY( !offerListHasService( offers, "khtml.desktop" ) ); // it got disabled above } void KServiceTest::testDefaultOffers() { // Now that we have a user-profile, let's see if defaultOffers indeed gives us the default ordering. const QString serviceType = "KParts/ReadOnlyPart"; KService::List offers = KServiceTypeTrader::self()->defaultOffers( serviceType ); QVERIFY( offers.count() > 0 ); // not empty QVERIFY( offerListHasService( offers, "khtml.desktop" ) ); // it's here even though it's disabled in the profile if ( m_firstOffer.isEmpty() ) QSKIP( "testServiceTypeTraderForReadOnlyPart not run", SkipAll ); QCOMPARE( offers[0]->entryPath(), m_firstOffer ); } void KServiceTest::testDeleteServiceTypeProfile() { const QString serviceType = "KParts/ReadOnlyPart"; KServiceTypeProfile::deleteServiceTypeProfile( serviceType ); KService::List offers = KServiceTypeTrader::self()->query( serviceType ); QVERIFY( offers.count() > 0 ); // not empty QVERIFY( offerListHasService( offers, "khtml.desktop" ) ); // it's back if ( m_firstOffer.isEmpty() ) QSKIP( "testServiceTypeTraderForReadOnlyPart not run", SkipAll ); QCOMPARE( offers[0]->entryPath(), m_firstOffer ); } void KServiceTest::testActionsAndDataStream() { const QString servicePath = KStandardDirs::locate( "services", "ScreenSavers/krandom.desktop" ); if (servicePath.isEmpty() ) QSKIP("kdebase not installed, krandom.desktop not found", SkipAll); KService service( servicePath ); QVERIFY(!service.property("Name[fr]", QVariant::String).isValid()); const QList actions = service.actions(); QCOMPARE(actions.count(), 3); const KServiceAction setupAction = actions[0]; QCOMPARE(setupAction.name(), QString("Setup")); QCOMPARE(setupAction.exec(), QString("krandom.kss -setup")); QVERIFY(!setupAction.icon().isEmpty()); QCOMPARE(setupAction.noDisplay(), false); QVERIFY(!setupAction.isSeparator()); const KServiceAction rootAction = actions[2]; QCOMPARE(rootAction.name(), QString("Root")); QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); service.save(stream); QVERIFY(!data.isEmpty()); // The binary size of that KService in ksycoca was 3700 when storing all Name[...] translations! // Now down to 755. This is on x86, so make the assert for 1500 in case x86_64 needs more. QVERIFY(data.size() < 1500); QDataStream loadingStream(data); // loading must first get type, see KSycocaEntryPrivate::save // (the path that save writes out, is read by the KSycocaEntryPrivate ctor) qint32 type; loadingStream >> type; KService loadedService(loadingStream, 0); QCOMPARE(loadedService.name(), service.name()); QCOMPARE(loadedService.exec(), service.exec()); QCOMPARE(loadedService.actions().count(), 3); } void KServiceTest::testServiceGroups() { KServiceGroup::Ptr root = KServiceGroup::root(); QVERIFY(root); qDebug() << root->groupEntries().count(); KServiceGroup::Ptr group = root; QVERIFY(group); const KServiceGroup::List list = group->entries(true /* sorted */, true /* exclude no display entries */, false /* allow separators */, true /* sort by generic name */); kDebug() << list.count(); Q_FOREACH(KServiceGroup::SPtr s, list) { qDebug() << s->name() << s->entryPath(); } // No unit test here yet, but at least this can be valgrinded for errors. } void KServiceTest::testKSycocaUpdate() { kWarning(); KService::Ptr fakeService = KService::serviceByDesktopPath("fakeservice.desktop"); QVERIFY(fakeService); // see initTestCase; it should be found. // Test deleting a service const QString servPath = KStandardDirs::locateLocal("services", "fakeservice.desktop"); QVERIFY(QFile::exists(servPath)); QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList))); QVERIFY(spy.isValid()); QFile::remove(servPath); kDebug() << QThread::currentThread() << "executing kbuildsycoca"; QProcess::execute( KGlobal::dirs()->findExe(KBUILDSYCOCA_EXENAME) ); kDebug() << QThread::currentThread() << "done"; while (spy.isEmpty()) QTest::qWait(50); QVERIFY(!spy.isEmpty()); QVERIFY(!KService::serviceByDesktopPath("fakeservice.desktop")); // not in ksycoca anymore QVERIFY(spy[0][0].toStringList().contains("services")); kDebug() << QThread::currentThread() << "got signal ok"; spy.clear(); QVERIFY(fakeService); // the whole point of refcounting is that this KService instance is still valid. QVERIFY(!QFile::exists(servPath)); // Recreate it, for future tests createFakeService(); QVERIFY(QFile::exists(servPath)); kDebug() << QThread::currentThread() << "executing kbuildsycoca (2)"; QProcess::execute( KGlobal::dirs()->findExe(KBUILDSYCOCA_EXENAME) ); kDebug() << QThread::currentThread() << "done (2)"; while (spy.isEmpty()) QTest::qWait(50); kDebug() << QThread::currentThread() << "got signal ok (2)"; QVERIFY(spy[0][0].toStringList().contains("services")); if (QThread::currentThread() != QCoreApplication::instance()->thread()) m_sycocaUpdateDone.ref(); } void KServiceTest::createFakeService() { const QString fakeService = KStandardDirs::locateLocal("services", "fakeservice.desktop"); KDesktopFile file(fakeService); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "FakePlugin"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-Library", "fakeservice"); group.writeEntry("X-KDE-Version", "4.56"); group.writeEntry("ServiceTypes", "KTextEditor/Plugin"); group.writeEntry("MimeType", "text/plain;"); } #include #include // Testing for concurrent access to ksycoca from multiple threads // It's especially interesting to run this test as ./kservicetest testThreads // so that even the ksycoca initialization is happening from N threads at the same time. // Use valgrind --tool=helgrind to see the race conditions. void KServiceTest::testReaderThreads() { QThreadPool::globalInstance()->setMaxThreadCount(10); QList > futures; futures << QtConcurrent::run(this, &KServiceTest::testAllServices); futures << QtConcurrent::run(this, &KServiceTest::testAllServices); futures << QtConcurrent::run(this, &KServiceTest::testAllServices); futures << QtConcurrent::run(this, &KServiceTest::testHasServiceType1); futures << QtConcurrent::run(this, &KServiceTest::testAllServices); futures << QtConcurrent::run(this, &KServiceTest::testAllServices); Q_FOREACH(QFuture f, futures) // krazy:exclude=foreach f.waitForFinished(); QThreadPool::globalInstance()->setMaxThreadCount(1); // delete those threads } void KServiceTest::testThreads() { QThreadPool::globalInstance()->setMaxThreadCount(10); QList > futures; futures << QtConcurrent::run(this, &KServiceTest::testAllServices); futures << QtConcurrent::run(this, &KServiceTest::testHasServiceType1); futures << QtConcurrent::run(this, &KServiceTest::testKSycocaUpdate); futures << QtConcurrent::run(this, &KServiceTest::testTraderConstraints); while (m_sycocaUpdateDone == 0) // not using a bool, just to silence helgrind QTest::qWait(100); // process D-Bus events! kDebug() << "Joining all threads"; Q_FOREACH(QFuture f, futures) // krazy:exclude=foreach f.waitForFinished(); }