tests/auto/qvaluespacepublisher/tst_qvaluespacepublisher.cpp
changeset 0 876b1a06bc25
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/auto/qvaluespacepublisher/tst_qvaluespacepublisher.cpp	Wed Aug 25 15:49:42 2010 +0300
@@ -0,0 +1,723 @@
+/****************************************************************************
+**
+** 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 <qvaluespacesubscriber.h>
+#include <qvaluespacemanager_p.h>
+#include <qvaluespacepublisher.h>
+
+#include <QThread>
+#include <QVector>
+
+#include <QTest>
+#include <QDebug>
+#include <QSignalSpy>
+#include <QFile>
+
+#ifdef Q_OS_WIN
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#endif
+
+#include <QProcess>
+
+#define QTRY_COMPARE(a,e)                       \
+    for (int _i = 0; _i < 5000; _i += 100) {    \
+        if ((a) == (e)) break;                  \
+        QTest::qWait(100);                      \
+    }                                           \
+    QCOMPARE(a, e)
+
+#define QTRY_VERIFY(a)                       \
+    for (int _i = 0; _i < 5000; _i += 100) {    \
+        if (a) break;                  \
+        QTest::qWait(100);                      \
+    }                                           \
+    QVERIFY(a)
+
+QTM_USE_NAMESPACE
+class ChangeListener : public QObject
+{
+    Q_OBJECT
+Q_SIGNALS:
+    void interestChanged(const QString&, bool);
+};
+
+class tst_QValueSpacePublisher: public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void initTestCase();
+    void cleanupTestCase();
+
+    void testConstructor_data();
+    void testConstructor();
+    void testFilterConstructor_data();
+    void testFilterConstructor();
+    void testBaseConstructor();
+
+    void testSetValue_data();
+    void testSetValue();
+
+    void testSignals_data();
+    void testSignals();
+
+    void valuePermanence_data();
+    void valuePermanence();
+
+    void threads_data();
+    void threads();
+
+private:
+    int variantMetaTypeId;
+};
+
+Q_DECLARE_METATYPE(QAbstractValueSpaceLayer *)
+Q_DECLARE_METATYPE(QUuid)
+Q_DECLARE_METATYPE(QVariant)
+Q_DECLARE_METATYPE(QValueSpace::LayerOptions)
+
+void tst_QValueSpacePublisher::initTestCase()
+{
+    variantMetaTypeId = qRegisterMetaType<QVariant>("QVariant");
+
+#ifdef Q_OS_WIN
+    HKEY key;
+    long result = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Nokia",
+                               0, KEY_ALL_ACCESS, &key);
+    if (result == ERROR_SUCCESS) {
+        result = RegDeleteKey(key, L"QtMobility\\volatileContext");
+        result = RegDeleteKey(key, L"QtMobility\\nonVolatileContext");
+        result = RegDeleteKey(key, L"QtMobility");
+
+        RegCloseKey(key);
+    }
+#endif
+
+#ifdef Q_OS_UNIX
+    QFile::remove("/tmp/qt-0/valuespace_shmlayer");
+#endif
+
+    QValueSpace::initValueSpaceServer();
+
+    if (QValueSpace::availableLayers().contains(QVALUESPACE_GCONF_LAYER)) {
+        QCOMPARE(QProcess::execute("gconftool-2 -u /value"), 0);
+        QCOMPARE(QProcess::execute("gconftool-2 -u /testConstructor/value"), 0);
+        QCOMPARE(QProcess::execute("gconftool-2 -u /testConstructor/subpath/value"), 0);
+    }
+
+}
+
+void tst_QValueSpacePublisher::cleanupTestCase()
+{
+}
+
+#define ADD(layer, id, path, canonical, valid) do {\
+    const QString layerName(layer ? layer->name() : 0); \
+    QTest::newRow((layerName + ' ' + path + " const QString &").toLocal8Bit().constData()) \
+        << layer << id << path << canonical << valid; \
+} while (false)
+
+void tst_QValueSpacePublisher::testConstructor_data()
+{
+    QTest::addColumn<QAbstractValueSpaceLayer *>("layer");
+    QTest::addColumn<QUuid>("uuid");
+
+    QTest::addColumn<QString>("path");
+    QTest::addColumn<QString>("canonical");
+    QTest::addColumn<bool>("connected");
+
+    QList<QAbstractValueSpaceLayer *> layers = QValueSpaceManager::instance()->getLayers();
+
+    // add all known layers
+    for (int i = 0; i < layers.count(); ++i) {
+        QAbstractValueSpaceLayer *layer = layers.at(i);
+
+        ADD(layer, layer->id(), QString(""), QString("/"), true);
+        ADD(layer, layer->id(), QString("/"), QString("/"), true);
+        ADD(layer, layer->id(), QString("//"), QString("/"), true);
+        ADD(layer, layer->id(), QString("/testConstructor"), QString("/testConstructor"), true);
+        ADD(layer, layer->id(), QString("/testConstructor/"), QString("/testConstructor"), true);
+        ADD(layer, layer->id(), QString("testConstructor"), QString("/testConstructor"), true);
+        ADD(layer, layer->id(), QString("/testConstructor/subpath"),\
+                                QString("/testConstructor/subpath"), true);
+    }
+
+    // unknown uuid
+    ADD(reinterpret_cast<QAbstractValueSpaceLayer *>(0),
+        QUuid("{9fa51477-7730-48e0-aee1-3eeb5f0c0c5b}"), QString(), QString("/"), false);
+}
+
+#undef ADD
+
+void tst_QValueSpacePublisher::testConstructor()
+{
+    QFETCH(QAbstractValueSpaceLayer *, layer);
+    QFETCH(QUuid, uuid);
+
+    QFETCH(QString, path);
+    QFETCH(QString, canonical);
+    QFETCH(bool, connected);
+
+    QValueSpacePublisher *publisher = new QValueSpacePublisher(uuid, path);
+
+    QCOMPARE(publisher->path(), canonical);
+    QCOMPARE(publisher->isConnected(), connected);
+
+    if (layer) {
+        QAbstractValueSpaceLayer::Handle handle =
+            layer->item(QAbstractValueSpaceLayer::InvalidHandle, canonical.toUtf8());
+
+        QVariant data;
+        QVERIFY(!layer->value(handle, "/value", &data));
+
+        layer->removeHandle(handle);
+    }
+
+    publisher->setValue(QString("value"), 100);
+    publisher->sync();
+
+    if (layer) {
+        QAbstractValueSpaceLayer::Handle handle =
+            layer->item(QAbstractValueSpaceLayer::InvalidHandle, canonical.toUtf8());
+
+        QVariant data;
+        QVERIFY(layer->value(handle, "/value", &data));
+        QCOMPARE(data.toInt(), 100);
+
+        layer->removeHandle(handle);
+    }
+
+    publisher->resetValue(QString("value"));
+    publisher->sync();
+
+    if (layer) {
+        QAbstractValueSpaceLayer::Handle handle =
+            layer->item(QAbstractValueSpaceLayer::InvalidHandle, canonical.toUtf8());
+
+        QVariant data;
+        QVERIFY(!layer->value(handle, "/value", &data));
+
+        layer->removeHandle(handle);
+    }
+
+    delete publisher;
+
+    if (layer && layer->layerOptions() & QValueSpace::PermanentLayer) {
+        QValueSpacePublisher root(uuid, "/");
+        while (!canonical.isEmpty()) {
+            root.resetValue(canonical.mid(1));
+            canonical.truncate(canonical.lastIndexOf('/'));
+        }
+        root.sync();
+    }
+}
+
+#define ADD(opt, valid) do {\
+    QTest::newRow(QString::number(opt).append(" const QString &").toLocal8Bit().constData()) \
+        << (QValueSpace::UnspecifiedLayer | opt) << valid; \
+} while (false)
+
+void tst_QValueSpacePublisher::testFilterConstructor_data()
+{
+    QTest::addColumn<QValueSpace::LayerOptions>("options");
+    QTest::addColumn<bool>("connected");
+
+    QList<QAbstractValueSpaceLayer *> layers = QValueSpaceManager::instance()->getLayers();
+
+    for (int i = 0; i < layers.count(); ++i) {
+        QAbstractValueSpaceLayer *layer = layers.at(i);
+
+        ADD(layer->layerOptions(), true);
+    }
+
+    ADD(QValueSpace::PermanentLayer | QValueSpace::TransientLayer,
+        false);
+    ADD(QValueSpace::WritableLayer | QValueSpace::ReadOnlyLayer,
+        false);
+}
+
+void tst_QValueSpacePublisher::testFilterConstructor()
+{
+    QFETCH(QValueSpace::LayerOptions, options);
+    QFETCH(bool, connected);
+
+    QValueSpacePublisher *publisher = new QValueSpacePublisher(options, QString("/"));
+
+    QCOMPARE(publisher->isConnected(), connected);
+}
+
+void tst_QValueSpacePublisher::testBaseConstructor()
+{
+    {
+        QValueSpacePublisher publisher("/");
+        QVERIFY(publisher.isConnected());
+    }
+
+    {
+        QValueSpacePublisher publisher(QString("/"));
+        QVERIFY(publisher.isConnected());
+    }
+}
+
+void tst_QValueSpacePublisher::testSetValue_data()
+{
+    QTest::addColumn<QAbstractValueSpaceLayer *>("layer");
+
+    QTest::addColumn<QString>("value");
+
+    QList<QAbstractValueSpaceLayer *> layers = QValueSpaceManager::instance()->getLayers();
+
+    for (int i = 0; i < layers.count(); ++i) {
+        QAbstractValueSpaceLayer *layer = layers.at(i);
+
+        QTest::newRow("empty") << layer << QString::fromLatin1("/");
+    }
+}
+
+void tst_QValueSpacePublisher::testSetValue()
+{
+    QFETCH(QAbstractValueSpaceLayer *, layer);
+    QFETCH(QString, value);
+
+    QValueSpaceSubscriber subscriber(layer->id(), QLatin1String("/testSetValue"));
+    QVERIFY(subscriber.subPaths().isEmpty());
+
+    QValueSpacePublisher publisher(layer->id(), QLatin1String("/testSetValue"));
+
+    publisher.setValue(QLatin1String(""), QLatin1String("default data"));
+    publisher.sync();
+    QVERIFY(subscriber.subPaths().isEmpty());
+    QCOMPARE(subscriber.value(QLatin1String("")).toString(), QLatin1String("default data"));
+
+    publisher.setValue(QLatin1String("key"), QLatin1String("key data"));
+    publisher.sync();
+    QCOMPARE(subscriber.subPaths().count(), 1);
+    QCOMPARE(subscriber.subPaths().first(), QLatin1String("key"));
+    QCOMPARE(subscriber.value(QLatin1String("key")).toString(), QLatin1String("key data"));
+
+    publisher.resetValue(QLatin1String("key"));
+    publisher.resetValue(QLatin1String(""));
+    publisher.sync();
+
+    QVERIFY(!subscriber.value(QLatin1String("")).isValid());
+    QVERIFY(!subscriber.value(QLatin1String("key")).isValid());
+}
+
+
+void tst_QValueSpacePublisher::testSignals_data()
+{
+    QTest::addColumn<QAbstractValueSpaceLayer *>("layer");
+
+    QTest::addColumn<QString>("publisherPath");
+    QTest::addColumn<QString>("subscriberPath");
+    QTest::addColumn<QString>("attribute");
+
+    QList<QAbstractValueSpaceLayer *> layers = QValueSpaceManager::instance()->getLayers();
+
+    bool foundSupported = false;
+
+    for (int i = 0; i < layers.count(); ++i) {
+        QAbstractValueSpaceLayer *layer = layers.at(i);
+
+        if (!layer->supportsInterestNotification())
+            continue;
+
+        foundSupported = true;
+
+        QTest::newRow("/ /")
+            << layer
+            << QString("/")
+            << QString("/")
+            << QString();
+
+        QTest::newRow("/ /testSignals")
+            << layer
+            << QString("/")
+            << QString("/testSignals")
+            << QString("testSignals");
+
+        QTest::newRow("/testSignals /testSignals")
+            << layer
+            << QString("/testSignals")
+            << QString("/testSignals")
+            << QString();
+    }
+
+    if (!foundSupported)
+        QSKIP("No layer supporting interest notifications found.", SkipAll);
+}
+
+void tst_QValueSpacePublisher::testSignals()
+{
+    QFETCH(QAbstractValueSpaceLayer *, layer);
+
+    QFETCH(QString, publisherPath);
+    QFETCH(QString, subscriberPath);
+    QFETCH(QString, attribute);
+
+    QValueSpacePublisher *publisher = new QValueSpacePublisher(layer->id(), publisherPath);
+
+    ChangeListener listener;
+    connect(publisher, SIGNAL(interestChanged(QString,bool)),
+            &listener, SIGNAL(interestChanged(QString,bool)));
+
+    QSignalSpy interestChangedSpy(&listener, SIGNAL(interestChanged(QString,bool)));
+
+    QValueSpaceSubscriber *subscriber = new QValueSpaceSubscriber(layer->id(), subscriberPath);
+
+    QTRY_COMPARE(interestChangedSpy.count(), 1);
+
+    QList<QVariant> arguments = interestChangedSpy.takeFirst();
+    QCOMPARE(arguments.count(), 2);
+    QCOMPARE(arguments.at(0).type(), QVariant::String);
+    QCOMPARE(arguments.at(0).toString(), attribute);
+    QCOMPARE(arguments.at(1).type(), QVariant::Bool);
+    QVERIFY(arguments.at(1).toBool());
+
+    delete subscriber;
+
+    QTRY_COMPARE(interestChangedSpy.count(), 1);
+
+    arguments = interestChangedSpy.takeFirst();
+    QCOMPARE(arguments.count(), 2);
+    QCOMPARE(arguments.at(0).type(), QVariant::String);
+    QCOMPARE(arguments.at(0).toString(), attribute);
+    QCOMPARE(arguments.at(1).type(), QVariant::Bool);
+    QVERIFY(!arguments.at(1).toBool());
+
+    delete publisher;
+}
+
+void tst_QValueSpacePublisher::valuePermanence_data()
+{
+    QTest::addColumn<QAbstractValueSpaceLayer *>("layer");
+
+    QList<QAbstractValueSpaceLayer *> layers = QValueSpaceManager::instance()->getLayers();
+
+    // add all known layers
+    for (int i = 0; i < layers.count(); ++i) {
+        QAbstractValueSpaceLayer *layer = layers.at(i);
+
+        QTest::newRow(layer->name().toLocal8Bit().constData()) << layer;
+    }
+}
+
+void tst_QValueSpacePublisher::valuePermanence()
+{
+    QFETCH(QAbstractValueSpaceLayer *, layer);
+
+    QValueSpacePublisher *publisher = new QValueSpacePublisher(layer->id(), "/valuePermanence");
+
+    publisher->setValue("value", 10);
+
+    QValueSpaceSubscriber subscriber("/valuePermanence");
+    QCOMPARE(subscriber.value("value", 0).toInt(), 10);
+
+    delete publisher;
+
+    if (layer->layerOptions() & QValueSpace::PermanentLayer) {
+        // Permanent layer, check that value is still available after publisher is deleted.
+        QCOMPARE(subscriber.value("value", 0).toInt(), 10);
+
+        publisher = new QValueSpacePublisher(layer->id(), "/valuePermanence");
+
+        publisher->resetValue("value");
+
+        QCOMPARE(subscriber.value("value", 0).toInt(), 0);
+
+        publisher->resetValue(QString());
+
+        delete publisher;
+    } else {
+        // Non-permanent layer, check that value is not available after publisher is deleted.
+        QCOMPARE(subscriber.value("value", 0).toInt(), 0);
+    }
+}
+
+class WriteThread : public QThread
+{
+    Q_OBJECT
+
+public:
+    WriteThread(const QString &path, const QUuid &uuid, unsigned int count);
+    ~WriteThread();
+
+    void runSequential() { run(); }
+
+protected:
+    void run();
+
+private:
+    QString path;
+    unsigned int count;
+    QValueSpacePublisher *publisher;
+};
+
+WriteThread::WriteThread(const QString &path, const QUuid &uuid, unsigned int count)
+:   path(path), count(count)
+{
+    publisher = new QValueSpacePublisher(uuid, path, this);
+}
+
+WriteThread::~WriteThread()
+{
+#ifdef Q_OS_SYMBIAN
+    //Cleanup published values since the SymbianSettingLayer is permanent
+    const QString key("key%1");
+    for (unsigned int i = 0; i < count; ++i)
+        publisher->resetValue(key.arg(i));
+
+    publisher->sync();
+#endif
+}
+
+void WriteThread::run()
+{
+    const QString key("key%1");
+    const QString value("value%1");
+
+    for (unsigned int i = 0; i < count; ++i)
+        publisher->setValue(key.arg(i), value.arg(i));
+
+    publisher->sync();
+}
+
+void tst_QValueSpacePublisher::threads_data()
+{
+    QTest::addColumn<QUuid>("uuid");
+
+    QTest::addColumn<unsigned int>("threads");
+    QTest::addColumn<unsigned int>("count");
+    QTest::addColumn<bool>("sequential");
+
+    QList<QAbstractValueSpaceLayer *> layers = QValueSpaceManager::instance()->getLayers();
+
+    int foundLayers = 0;
+    for (int i = 0; i < layers.count(); ++i) {
+        QAbstractValueSpaceLayer *layer = layers.at(i);
+
+        if (layer->id() == QVALUESPACE_NONVOLATILEREGISTRY_LAYER)
+            continue;
+
+        //GConfLayer can't provide thread-safety because it eventually depends on
+        //DBus which isn't fully thread-safe
+        if (layer->id() == QVALUESPACE_GCONF_LAYER)
+            continue;
+
+#ifdef Q_OS_WINCE
+        // Limit number of items on Windows CE to prevent out of disk space errors.
+        if (layer->id() == QVALUESPACE_VOLATILEREGISTRY_LAYER) {
+            QTest::newRow("1 thread, 10 items, sequential")
+                << layer->id() << uint(1) << uint(10) << true;
+            QTest::newRow("1 thread, 3000 items, sequential")
+                << layer->id() << uint(1) << uint(3000) << true;
+            QTest::newRow("2 threads, 10 items, sequential")
+                << layer->id() << uint(2) << uint(10) << true;
+            QTest::newRow("2 threads, 1500 items, sequential")
+                << layer->id() << uint(2) << uint(1500) << true;
+            QTest::newRow("4 threads, 750 items, sequential")
+                << layer->id() << uint(4) << uint(750) << true;
+            QTest::newRow("10 threads, 300 items, sequential")
+                << layer->id() << uint(10) << uint(300) << true;
+
+            QTest::newRow("1 thread, 10 items")
+                << layer->id() << uint(1) << uint(10) << false;
+            QTest::newRow("1 thread, 3000 items")
+                << layer->id() << uint(1) << uint(3000) << false;
+            QTest::newRow("2 threads, 10 items")
+                << layer->id() << uint(2) << uint(10) << false;
+            QTest::newRow("2 threads, 1500 items")
+                << layer->id() << uint(2) << uint(1500) << false;
+            QTest::newRow("4 threads, 750 items")
+                << layer->id() << uint(4) << uint(750) << false;
+            QTest::newRow("10 threads, 300 items")
+                << layer->id() << uint(10) << uint(300) << false;
+        } else
+#endif
+
+        // The Shared Memory layer can hold a maximum of 8191 nodes.
+        if (layer->id() == QVALUESPACE_SHAREDMEMORY_LAYER) {
+            QTest::newRow("1 thread, 10 items, sequential")
+                << layer->id() << uint(1) << uint(10) << true;
+            QTest::newRow("1 thread, 8000 items, sequential")
+                << layer->id() << uint(1) << uint(8000) << true;
+            QTest::newRow("2 threads, 10 items, sequential")
+                << layer->id() << uint(2) << uint(10) << true;
+            QTest::newRow("2 threads, 4000 items, sequential")
+                << layer->id() << uint(2) << uint(4000) << true;
+            QTest::newRow("4 threads, 2000 items, sequential")
+                << layer->id() << uint(4) << uint(2000) << true;
+            QTest::newRow("10 threads, 800 items, sequential")
+                << layer->id() << uint(10) << uint(800) << true;
+            QTest::newRow("100 threads, 80 items, sequential")
+                << layer->id() << uint(100) << uint(80) << true;
+
+            QTest::newRow("1 thread, 10 items")
+                << layer->id() << uint(1) << uint(10) << false;
+            QTest::newRow("1 thread, 8000 items")
+                << layer->id() << uint(1) << uint(8000) << false;
+            QTest::newRow("2 threads, 10 items")
+                << layer->id() << uint(2) << uint(10) << false;
+            QTest::newRow("2 threads, 4000 items")
+                << layer->id() << uint(2) << uint(4000) << false;
+            QTest::newRow("4 threads, 2000 items")
+                << layer->id() << uint(4) << uint(2000) << false;
+            QTest::newRow("10 threads, 800 items")
+                << layer->id() << uint(10) << uint(800) << false;
+            QTest::newRow("100 threads, 80 items")
+                << layer->id() << uint(100) << uint(80) << false;
+        } else if (layer->id() == QVALUESPACE_SYMBIAN_SETTINGS_LAYER) {
+            QTest::newRow("1 thread, 10 items, sequential")
+                << layer->id() << uint(1) << uint(10) << true;
+            QTest::newRow("2 threads, 10 items, sequential")
+                << layer->id() << uint(2) << uint(10) << true;
+        } else {
+            // Assume no limits on all other layers.
+            QTest::newRow("1 thread, 10 items, sequential")
+                << layer->id() << uint(1) << uint(10) << true;
+            QTest::newRow("1 thread, 10000 items, sequential")
+                << layer->id() << uint(1) << uint(10000) << true;
+            QTest::newRow("2 threads, 10 items, sequential")
+                << layer->id() << uint(2) << uint(10) << true;
+            QTest::newRow("2 threads, 5000 items, sequential")
+                << layer->id() << uint(2) << uint(5000) << true;
+            QTest::newRow("100 threads, 100 items, sequential")
+                << layer->id() << uint(100) << uint(100) << true;
+
+            QTest::newRow("1 thread, 10 items")
+                << layer->id() << uint(1) << uint(10) << false;
+            QTest::newRow("1 thread, 10000 items")
+                << layer->id() << uint(1) << uint(10000) << false;
+            QTest::newRow("2 threads, 10 items")
+                << layer->id() << uint(2) << uint(10) << false;
+            QTest::newRow("2 threads, 5000 items")
+                << layer->id() << uint(2) << uint(5000) << false;
+            QTest::newRow("100 threads, 100 items")
+                << layer->id() << uint(100) << uint(100) << false;
+        }
+        foundLayers++;
+    }
+
+    if (foundLayers == 0)
+        QSKIP("No layers providing thread-safety found", SkipAll);
+}
+
+void tst_QValueSpacePublisher::threads()
+{
+    QFETCH(QUuid, uuid);
+    QFETCH(unsigned int, threads);
+    QFETCH(unsigned int, count);
+    QFETCH(bool, sequential);
+
+    if (QValueSpace::availableLayers().contains(QVALUESPACE_GCONF_LAYER)) {
+        QCOMPARE(QProcess::execute("gconftool-2 --recursive-unset /threads"), 0);
+    }
+    QStringList expectedPaths;
+    for (unsigned int i = 0; i < threads; ++i)
+        expectedPaths.append(QString("thread%1").arg(i));
+
+    QHash<QString, QString> expectedValues;
+    for (unsigned int i = 0; i < count; ++i)
+        expectedValues.insert(QString("key%1").arg(i), QString("value%1").arg(i));
+
+    QValueSpaceSubscriber *subscriber = new QValueSpaceSubscriber(uuid, "/threads");
+
+    QVERIFY(subscriber->subPaths().isEmpty());
+
+    QVector<WriteThread *> writeThreads(threads);
+
+    // Create and start writer threads.
+    for (unsigned int i = 0; i < threads; ++i) {
+        writeThreads[i] =
+            new WriteThread(QString("/threads/%1").arg(expectedPaths.at(i)), uuid, count);
+
+        if (sequential)
+            writeThreads[i]->runSequential();
+        else
+            writeThreads[i]->start();
+    }
+
+    if (!sequential) {
+        // Wait for writer threads to finish.
+        for (unsigned int i = 0; i < threads; ++i)
+            writeThreads[i]->wait();
+    }
+
+    qDebug() << "Published" << count << "items in" << threads
+             << "theads, totaling" << (threads * count);
+
+    // Verify Value Space
+    QStringList subPaths = subscriber->subPaths();
+    if (subPaths.toSet() != expectedPaths.toSet()) {
+        qDebug() << "Expected Paths:" << expectedPaths;
+        qDebug() << "Actual Paths:" << subPaths;
+    }
+    QVERIFY(subPaths.toSet() == expectedPaths.toSet());
+
+    while (!subPaths.isEmpty()) {
+        QValueSpaceSubscriber threadItem;
+        threadItem.setPath(subscriber);
+        threadItem.cd(subPaths.takeFirst());
+
+        QStringList keys = threadItem.subPaths();
+
+        if (keys.toSet() != expectedValues.keys().toSet()) {
+            qDebug() << "Expected value keys:" << expectedValues.keys();
+            qDebug() << "Actual value keys:" << keys;
+        }
+        QVERIFY(keys.toSet() == expectedValues.keys().toSet());
+
+        while (!keys.isEmpty()) {
+            const QString key = keys.takeFirst();
+            QCOMPARE(threadItem.value(key).toString(), expectedValues.value(key));
+        }
+    }
+
+    delete subscriber;
+
+    // Delete writer threads.
+    for (unsigned int i = 0; i < threads; ++i)
+        delete writeThreads[i];
+}
+
+QTEST_MAIN(tst_QValueSpacePublisher)
+#include "tst_qvaluespacepublisher.moc"