Add new tests and update the existing one.
authorThiago Macieira <thiago@kde.org>
Wed, 15 Feb 2006 17:06:41 +0000 (17:06 +0000)
committerThiago Macieira <thiago@kde.org>
Wed, 15 Feb 2006 17:06:41 +0000 (17:06 +0000)
test/qt/Makefile.am
test/qt/tst_hal.cpp [new file with mode: 0644]
test/qt/tst_qdbusconnection.cpp
test/qt/tst_qdbusinterface.cpp [new file with mode: 0644]
test/qt/tst_qdbusobject.cpp [new file with mode: 0644]
test/qt/tst_qdbustype.cpp [new file with mode: 0644]
test/qt/tst_qdbusxmlparser.cpp [new file with mode: 0644]

index 4ada01d..77f86d8 100644 (file)
@@ -1,7 +1,7 @@
 INCLUDES=-I$(top_srcdir) -I$(top_srcdir)/qt $(DBUS_CLIENT_CFLAGS) $(DBUS_QT_CFLAGS) $(DBUS_QTESTLIB_CFLAGS) -DDBUS_COMPILATION
 
 if DBUS_BUILD_TESTS
-TEST_BINARIES=qdbusconnection
+TEST_BINARIES=qdbusconnection qdbusobject qdbusinterface qdbustype qdbusxmlparser hal
 TESTS=
 else
 TEST_BINARIES=
@@ -11,16 +11,25 @@ endif
 
 noinst_PROGRAMS= $(TEST_BINARIES)
 
-qdbusconnection_SOURCES= \
-       tst_qdbusconnection.cpp
+qdbusconnection_SOURCES= tst_qdbusconnection.cpp
+qdbusobject_SOURCES= tst_qdbusobject.cpp
+qdbusinterface_SOURCES= tst_qdbusinterface.cpp
+qdbustype_SOURCES= tst_qdbustype.cpp
+qdbusxmlparser_SOURCES= tst_qdbusxmlparser.cpp
+hal_SOURCES = tst_hal.cpp
 
-tst_qdbusconnection.cpp: tst_qdbusconnection.moc
+tst_qdbusconnection.o: tst_qdbusconnection.moc
+tst_qdbusobject.o: tst_qdbusobject.moc
+tst_qdbusinterface.o: tst_qdbusinterface.moc
+tst_qdbustype.o: tst_qdbustype.moc
+tst_qdbusxmlparser.o: tst_qdbusxmlparser.moc
+tst_hal.o: tst_hal.moc
 
-tst_qdbusconnection.moc:
-       $(QT_MOC) $(srcdir)/tst_qdbusconnection.cpp > tst_qdbusconnection.moc
+%.moc: %.cpp
+       $(QT_MOC) $< > $@
 
 TEST_LIBS=$(DBUS_QTESTLIB_LIBS) $(top_builddir)/qt/libdbus-qt4-1.la
 
-qdbusconnection_LDADD=$(TEST_LIBS)
+LDADD=$(TEST_LIBS)
 
 CLEANFILES=tst_qdbusconnection.moc
diff --git a/test/qt/tst_hal.cpp b/test/qt/tst_hal.cpp
new file mode 100644 (file)
index 0000000..36389c2
--- /dev/null
@@ -0,0 +1,78 @@
+#include <qcoreapplication.h>
+#include <qdebug.h>
+
+#include <QtTest/QtTest>
+#include <dbus/qdbus.h>
+
+class tst_Hal: public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void getDevices();
+    void lock();
+};
+
+class Spy: public QObject
+{
+    Q_OBJECT
+public:
+    int count;
+    QDBusConnection conn;
+
+    Spy(QDBusConnection c) : count(0), conn(c)
+    { }
+
+public slots:
+    void spySlot(int, const QVariantList&)
+    {
+        ++count;
+        QDBusMessage msg = QDBusMessage::methodCall("org.freedesktop.Hal",
+                        "/org/freedesktop/Hal/devices/acpi_CPU0",
+                        "org.freedesktop.Hal.Device", "GetProperty");
+        msg << "info.locked";
+
+        QDBusMessage reply = conn.sendWithReply(msg);
+        QVERIFY(!reply.isEmpty());
+    }
+};
+
+
+void tst_Hal::getDevices()
+{
+    QDBusConnection con = QDBusConnection::addConnection(QDBusConnection::SystemBus);
+    QVERIFY(con.isConnected());
+
+    QDBusMessage msg = QDBusMessage::methodCall("org.freedesktop.Hal",
+                "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager",
+                "GetAllDevices");
+
+    QDBusMessage reply = con.sendWithReply(msg);
+    QVERIFY(!reply.isEmpty());
+    qDebug() << reply;
+}
+
+void tst_Hal::lock()
+{
+    QDBusConnection con = QDBusConnection::addConnection(QDBusConnection::SystemBus);
+    QVERIFY(con.isConnected());
+
+    Spy spy( con );
+
+    con.connect("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/acpi_CPU0",
+                "org.freedesktop.Hal.Device", "PropertyModified",
+                &spy, SLOT(spySlot(int, QVariantList)));
+    QDBusMessage msg = QDBusMessage::methodCall("org.freedesktop.Hal",
+                "/org/freedesktop/Hal/devices/acpi_CPU0", "org.freedesktop.Hal.Device",
+                "Lock");
+    msg << "No reason...";
+
+    QDBusMessage reply = con.sendWithReply(msg);
+    qDebug() << reply;
+    QCOMPARE(spy.count, 3);
+    QCOMPARE(reply.type(), QDBusMessage::ReplyMessage);
+}
+
+QTEST_MAIN(tst_Hal)
+
+#include "tst_hal.moc"
index e14debd..c3b15a2 100644 (file)
@@ -10,11 +10,19 @@ class tst_QDBusConnection: public QObject
     Q_OBJECT
 
 private slots:
+    void init();
+    void cleanupTestCase();
     void addConnection();
     void connect();
     void send();
     void sendAsync();
     void sendSignal();
+    void requestName_data();
+    void requestName();
+    void getNameOwner_data();
+    void getNameOwner();
+    void releaseName_data();
+    void releaseName();
 };
 
 class QDBusSpy: public QObject
@@ -29,10 +37,25 @@ public:
     int serial;
 };
 
+void tst_QDBusConnection::init()
+{
+    if (qstrcmp(QTest::currentTestFunction(), "addConnection") == 0)
+        return;
+
+    QDBusConnection::addConnection(QDBusConnection::SessionBus);
+    QVERIFY(QDBusConnection().isConnected());
+}
+
+void tst_QDBusConnection::cleanupTestCase()
+{
+    QDBusConnection::closeConnection();
+
+    QVERIFY(!QDBusConnection().isConnected());
+}
+
 void tst_QDBusConnection::sendSignal()
 {
-    QDBusConnection con = QDBusConnection::addConnection(
-            QDBusConnection::SessionBus);
+    QDBusConnection con;
 
     QVERIFY(con.isConnected());
 
@@ -47,8 +70,7 @@ void tst_QDBusConnection::sendSignal()
 
 void tst_QDBusConnection::send()
 {
-    QDBusConnection con = QDBusConnection::addConnection(
-            QDBusConnection::SessionBus);
+    QDBusConnection con;
 
     QVERIFY(con.isConnected());
 
@@ -64,7 +86,7 @@ void tst_QDBusConnection::send()
 
 void tst_QDBusConnection::sendAsync()
 {
-    QDBusConnection con = QDBusConnection::addConnection(QDBusConnection::SessionBus);
+    QDBusConnection con;
     QVERIFY(con.isConnected());
 
     QDBusSpy spy;
@@ -85,10 +107,9 @@ void tst_QDBusConnection::connect()
 {
     QDBusSpy spy;
 
-    QDBusConnection con = QDBusConnection::addConnection(
-            QDBusConnection::SessionBus);
+    QDBusConnection con;
 
-    con.connect("/org/kde/selftest", "org.kde.selftest", "ping", &spy,
+    con.connect(con.baseService(), "/org/kde/selftest", "org.kde.selftest", "ping", &spy,
                  SLOT(handlePing(QString)));
 
     QDBusMessage msg = QDBusMessage::signal("/org/kde/selftest", "org.kde.selftest",
@@ -158,6 +179,89 @@ void tst_QDBusConnection::addConnection()
     }
 }
 
+void tst_QDBusConnection::requestName_data()
+{
+    QTest::addColumn<QString>("requestedName");
+    QTest::addColumn<int>("flags");
+    QTest::addColumn<bool>("expectedResult");
+
+    QTest::newRow("null") << QString() << (int)QDBusConnection::NoReplace << false;
+    QTest::newRow("empty") << QString("") << (int)QDBusConnection::NoReplace << false;
+    QTest::newRow("invalid") << "./invalid name" << (int)QDBusConnection::NoReplace << false;
+//    QTest::newRow("existing") << "org.freedesktop.DBus"
+//                              << (int)QDBusConnection::NoReplace << false;
+
+    QTest::newRow("ok1") << "com.trolltech.QtDBUS.tst_qdbusconnection"
+                         << (int)QDBusConnection::NoReplace << true;
+}
+
+void tst_QDBusConnection::requestName()
+{
+    QDBusConnection con;
+
+    QVERIFY(con.isConnected());
+    
+    QFETCH(QString, requestedName);
+    QFETCH(int, flags);
+    QFETCH(bool, expectedResult);
+
+    bool result = con.requestName(requestedName, (QDBusConnection::NameRequestMode)flags);
+
+//    QEXPECT_FAIL("existing", "For whatever reason, the bus lets us replace this name", Abort);
+    QCOMPARE(result, expectedResult);
+}
+
+void tst_QDBusConnection::getNameOwner_data()
+{
+    QTest::addColumn<QString>("name");
+    QTest::addColumn<QString>("expectedResult");
+
+    QTest::newRow("null") << QString() << QString();
+    QTest::newRow("empty") << QString("") << QString();
+
+    QTest::newRow("invalid") << ".invalid" << QString();
+    QTest::newRow("non-existent") << "com.trolltech.QtDBUS.foo" << QString();
+
+    QTest::newRow("bus") << "org.freedesktop.DBus" << "org.freedesktop.DBus";
+
+    QString base = QDBusConnection().baseService();
+    QTest::newRow("address") << base << base;
+    QTest::newRow("self") << "com.trolltech.QtDBUS.tst_qdbusconnection" << base;
+}
+
+void tst_QDBusConnection::getNameOwner()
+{
+    QFETCH(QString, name);
+    QFETCH(QString, expectedResult);
+
+    QDBusConnection con;
+    QVERIFY(con.isConnected());
+
+    QString result = con.getNameOwner(name);
+
+    QCOMPARE(result, expectedResult);
+}
+
+void tst_QDBusConnection::releaseName_data()
+{
+    requestName_data();
+}
+
+void tst_QDBusConnection::releaseName()
+{
+    QDBusConnection con;
+
+    QVERIFY(con.isConnected());
+    
+    QFETCH(QString, requestedName);
+    //QFETCH(int, flags);
+    QFETCH(bool, expectedResult);
+
+    bool result = con.releaseName(requestedName);
+
+    QCOMPARE(result, expectedResult);
+}    
+
 QTEST_MAIN(tst_QDBusConnection)
 
 #include "tst_qdbusconnection.moc"
diff --git a/test/qt/tst_qdbusinterface.cpp b/test/qt/tst_qdbusinterface.cpp
new file mode 100644 (file)
index 0000000..599b6c0
--- /dev/null
@@ -0,0 +1,325 @@
+/* -*- C++ -*-
+ *
+ * Copyright (C) 2006 Trolltech AS. All rights reserved.
+ *    Author: Thiago Macieira <thiago.macieira@trolltech.com>
+ *
+ * Licensed under the Academic Free License version 2.1
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+#include <qcoreapplication.h>
+#include <qmetatype.h>
+#include <QtTest/QtTest>
+
+#include <dbus/qdbus.h>
+#include <QtCore/qvariant.h>
+
+Q_DECLARE_METATYPE(QVariantList)
+
+#define TEST_INTERFACE_NAME "com.trolltech.QtDBus.MyObject"
+#define TEST_SERVICE_NAME "com.trolltech.QtDBus.tst_qdbusinterface"
+#define TEST_SIGNAL_NAME "somethingHappened"
+
+const char introspectionData[] =
+    "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
+    "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
+    "<node>"
+
+    "<interface name=\"org.freedesktop.DBus.Introspectable\">"
+    "<method name=\"Introspect\">"
+    "<arg name=\"data\" direction=\"out\" type=\"s\"/>"
+    "</method>"
+    "</interface>"
+
+    "<interface name=\"" TEST_INTERFACE_NAME "\">"
+    "<method name=\"ping\">"
+    "<arg name=\"ping\" direction=\"in\"  type=\"v\"/>"
+    "<arg name=\"pong\" direction=\"out\" type=\"v\"/>"
+    "</method>"
+    "<method name=\"ping\">"
+    "<arg name=\"ping1\" direction=\"in\"  type=\"v\"/>"
+    "<arg name=\"ping2\" direction=\"in\"  type=\"v\"/>"
+    "<arg name=\"pong1\" direction=\"out\" type=\"v\"/>"
+    "<arg name=\"pong2\" direction=\"out\" type=\"v\"/>"
+    "</method>"
+    "<signal name=\"" TEST_SIGNAL_NAME "\">"
+    "<arg type=\"s\"/>"
+    "</signal>"
+    "<property name=\"prop1\" access=\"readwrite\" type=\"i\" />"
+    "</interface>"
+    "<node name=\"subObject\"/>"
+    "</node>";
+
+class MyObject: public QObject
+{
+    Q_OBJECT
+public slots:
+
+    void ping(const QDBusMessage &msg)
+    {
+        QDBusConnection con = QDBusConnection::addConnection(QDBusConnection::SessionBus);
+        QDBusMessage reply = QDBusMessage::methodReply(msg);
+        reply << static_cast<QList<QVariant> >(msg);
+        if (!con.send(reply))
+            exit(1);
+    }
+
+    void Introspect(const QDBusMessage &msg)
+    {
+        QDBusConnection con = QDBusConnection::addConnection(QDBusConnection::SessionBus);
+        QDBusMessage reply = QDBusMessage::methodReply(msg);
+        reply << ::introspectionData;
+        if (!con.send(reply))
+            exit(1);
+    }
+};
+
+class Spy: public QObject
+{
+    Q_OBJECT
+public:
+    QString received;
+    int count;
+
+    Spy() : count(0)
+    { }
+
+public slots:
+    void spySlot(const QString& arg)
+    {
+        received = arg;
+        ++count;
+    }
+};
+
+// helper function
+void emitSignal(const QString &interface, const QString &name, const QString &arg)
+{
+    QDBusMessage msg = QDBusMessage::signal("/", interface, name);
+    msg << arg;
+    QDBusConnection().send(msg);
+
+    QTest::qWait(200);
+}
+
+class tst_QDBusInterface: public QObject
+{
+    Q_OBJECT
+    MyObject obj;
+private slots:
+    void initTestCase();
+    void cleanupTestCase();
+
+    void call_data();
+    void call();
+
+    void introspect_data();
+    void introspect();
+
+    void signal();
+};
+
+void tst_QDBusInterface::initTestCase()
+{
+    QDBusConnection con = QDBusConnection::addConnection(QDBusConnection::SessionBus);
+    QVERIFY(con.isConnected());
+    QVERIFY(con.requestName( TEST_SERVICE_NAME ));
+
+    con.registerObject("/", "org.freedesktop.DBus.Introspectable", &obj);
+    con.registerObject("/", TEST_INTERFACE_NAME, &obj);
+}
+
+void tst_QDBusInterface::cleanupTestCase()
+{
+    QDBusConnection::closeConnection();
+    QVERIFY(!QDBusConnection().isConnected());
+}
+
+void tst_QDBusInterface::call_data()
+{
+    QTest::addColumn<QString>("method");
+    QTest::addColumn<QVariantList>("input");
+    QTest::addColumn<QVariantList>("output");
+
+    QVariantList input;
+    QTest::newRow("empty") << "ping" << input << input;
+
+    input << qVariantFromValue(1);
+    QTest::newRow("int") << "ping" << input << input;
+    QTest::newRow("int-int") << "ping.i" << input << input;
+    QTest::newRow("int-int16") << "ping.n" << input << input;
+
+    // try doing some conversions
+    QVariantList output;
+    output << qVariantFromValue(1U);
+    QTest::newRow("int-uint") << "ping.u" << input << output;
+    QTest::newRow("int-uint16") << "ping.q" << input << output;
+
+    QTest::newRow("int-int64") << "ping.x" << input << (QVariantList() << qVariantFromValue(1LL));
+    QTest::newRow("int-uint64") << "ping.t" << input << (QVariantList() << qVariantFromValue(1ULL));
+    QTest::newRow("int-double") << "ping.d" << input << (QVariantList() << qVariantFromValue(1.0));
+
+    output.clear();
+    output << QString("1");
+    QTest::newRow("int-string") << "ping.s" << input << output;
+
+    // try from string now
+    input = output;
+    QTest::newRow("string") << "ping" << input << output;
+    QTest::newRow("string-string") << "ping.s" << input << output;
+
+    output.clear();
+    output << qVariantFromValue(1);
+    QTest::newRow("string-int") << "ping.i" << input << input;
+    QTest::newRow("string-int16") << "ping.n" << input << input;
+
+    output.clear();
+    output << qVariantFromValue(1U);
+    QTest::newRow("string-uint") << "ping.u" << input << output;
+    QTest::newRow("string-uint16") << "ping.q" << input << output;
+
+    QTest::newRow("string-int64") << "ping.x" << input << (QVariantList() << qVariantFromValue(1LL));
+    QTest::newRow("string-uint64") << "ping.t" << input << (QVariantList() << qVariantFromValue(1ULL));
+    QTest::newRow("string-double") << "ping.d" << input << (QVariantList() << qVariantFromValue(1.0));
+
+    // two args (must be strings!)
+    input.clear();
+    input << QString("Hello") << QString("World");
+    output = input;
+    QTest::newRow("two-strings") << "ping" << input << output;
+    QTest::newRow("two-strings") << "ping.ss" << input << output;
+
+    // this should drop one of the arguments
+    output.removeLast();
+    QTest::newRow("last-dropped") << "ping.s" << input << output;
+}
+
+void tst_QDBusInterface::call()
+{
+    QDBusConnection con;
+    QDBusInterface iface(con, con.baseService(), QLatin1String("/"),
+                         TEST_INTERFACE_NAME);
+
+    QFETCH(QString, method);
+    QFETCH(QVariantList, input);
+    QFETCH(QVariantList, output);
+    
+    QDBusMessage reply;
+    // try first callWithArgs:
+    reply = iface.callWithArgs(method, input);
+
+    QCOMPARE(reply.type(), QDBusMessage::ReplyMessage);
+    if (!output.isEmpty()) {
+        QCOMPARE(reply.count(), output.count());
+        QCOMPARE(static_cast<QVariantList>(reply), output);
+    }
+
+    // try the template methods
+    if (input.isEmpty())
+        reply = iface.call(method);
+    else if (input.count() == 1)
+        switch (input.at(0).type())
+        {
+        case QVariant::Int:
+            reply = iface.call(method, input.at(0).toInt());
+            break;
+
+        case QVariant::UInt:
+            reply = iface.call(method, input.at(0).toUInt());
+            break;
+
+        case QVariant::String:
+            reply = iface.call(method, input.at(0).toString());
+            break;
+
+        default:
+            QFAIL("Unknown type. Please update the test case");
+            break;
+        }
+    else
+        reply = iface.call(method, input.at(0).toString(), input.at(1).toString());
+
+    QCOMPARE(reply.type(), QDBusMessage::ReplyMessage);
+    if (!output.isEmpty()) {
+        QCOMPARE(reply.count(), output.count());
+        QCOMPARE(static_cast<QVariantList>(reply), output);
+    }
+}
+
+void tst_QDBusInterface::introspect_data()
+{
+    QTest::addColumn<QString>("service");
+    QTest::newRow("base") << QDBusConnection().baseService();
+    QTest::newRow("name") << TEST_SERVICE_NAME;
+}
+
+void tst_QDBusInterface::introspect()
+{
+    QFETCH(QString, service);
+    QDBusConnection con;
+    QDBusInterface iface(con, service, QLatin1String("/"),
+                         TEST_INTERFACE_NAME);
+
+    QDBusIntrospection::Methods mm = iface.methodData();
+    QVERIFY(mm.count() == 2);
+
+    QDBusIntrospection::Signals sm = iface.signalData();
+    QVERIFY(sm.count() == 1);
+    QVERIFY(sm.contains(TEST_SIGNAL_NAME));
+
+    QDBusIntrospection::Properties pm = iface.propertyData();
+    QVERIFY(pm.count() == 1);
+    QVERIFY(pm.contains("prop1"));
+}
+
+void tst_QDBusInterface::signal()
+{
+    QDBusConnection con;
+    QDBusInterface iface(con, con.baseService(), QLatin1String("/"),
+                         TEST_INTERFACE_NAME);
+
+    QString signalName = TEST_SIGNAL_NAME;
+
+    QString arg = "So long and thanks for all the fish";
+    {
+        Spy spy;
+        iface.connect(signalName, &spy, SLOT(spySlot(QString)));
+
+        emitSignal(TEST_INTERFACE_NAME, signalName, arg);
+        QVERIFY(spy.count == 1);
+        QCOMPARE(spy.received, arg);
+    }
+
+    QDBusIntrospection::Signals sm = iface.signalData();
+    QVERIFY(sm.contains(signalName));
+
+    const QDBusIntrospection::Signal& signal = sm.value(signalName);
+    QCOMPARE(signal.name, signalName);
+    QVERIFY(!signal.outputArgs.isEmpty());
+    {
+        Spy spy;
+        iface.connect(signal, &spy, SLOT(spySlot(QString)));
+
+        emitSignal(TEST_INTERFACE_NAME, signalName, arg);
+        QVERIFY(spy.count == 1);
+        QCOMPARE(spy.received, arg);
+    }
+}
+
+QTEST_MAIN(tst_QDBusInterface)
+
+#include "tst_qdbusinterface.moc"
+
diff --git a/test/qt/tst_qdbusobject.cpp b/test/qt/tst_qdbusobject.cpp
new file mode 100644 (file)
index 0000000..24d5077
--- /dev/null
@@ -0,0 +1,198 @@
+/* -*- C++ -*-
+ *
+ * Copyright (C) 2006 Trolltech AS. All rights reserved.
+ *    Author: Thiago Macieira <thiago.macieira@trolltech.com>
+ *
+ * Licensed under the Academic Free License version 2.1
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+#include <qcoreapplication.h>
+#include <qmetatype.h>
+#include <QtTest/QtTest>
+
+#include <dbus/qdbus.h>
+
+const char introspectionData[] =
+    "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
+    "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
+    "<node>"
+
+    "<interface name=\"org.freedesktop.DBus.Introspectable\">"
+    "<method name=\"Introspect\">"
+    "<arg name=\"data\" direction=\"out\" type=\"s\"/>"
+    "</method>"
+    "</interface>"
+
+    "<interface name=\"com.trolltech.tst_qdbusobject.MyObject\">"
+    "<method name=\"ping\">"
+    "<arg name=\"ping\" direction=\"in\"  type=\"v\"/>"
+    "<arg name=\"pong\" direction=\"out\" type=\"v\"/>"
+    "</method>"
+    "</interface>"
+    "<node name=\"subObject\"/>"
+    "</node>";
+
+class MyObject: public QObject
+{
+    Q_OBJECT
+public slots:
+
+    void ping(const QDBusMessage &msg)
+    {
+        QDBusConnection con = QDBusConnection::addConnection(QDBusConnection::SessionBus);
+        QDBusMessage reply = QDBusMessage::methodReply(msg);
+        reply << static_cast<QList<QVariant> >(msg);
+        if (!con.send(reply))
+            exit(1);
+    }
+
+    void Introspect(const QDBusMessage &msg)
+    {
+        QDBusConnection con = QDBusConnection::addConnection(QDBusConnection::SessionBus);
+        QDBusMessage reply = QDBusMessage::methodReply(msg);
+        reply << ::introspectionData;
+        if (!con.send(reply))
+            exit(1);
+    }
+};
+
+class tst_QDBusObject: public QObject
+{
+    Q_OBJECT
+    MyObject obj;
+
+private slots:
+    void initTestCase();        // connect to D-Bus
+    void cleanupTestCase();     // disconnect from D-Bus
+
+    void construction_data();
+    void construction();
+
+    void introspection_data();
+    void introspection();
+};
+
+void tst_QDBusObject::initTestCase()
+{
+    QDBusConnection con = QDBusConnection::addConnection(QDBusConnection::SessionBus);
+    QVERIFY(con.isConnected());
+    QVERIFY(con.requestName("com.trolltech.tst_qdbusobject"));
+
+    con.registerObject("/", "org.freedesktop.DBus.Introspectable", &obj);
+    con.registerObject("/", "com.trolltech.tst_qdbusobject.MyObject", &obj);
+}
+
+void tst_QDBusObject::cleanupTestCase()
+{
+    QDBusConnection::closeConnection();
+    QVERIFY(!QDBusConnection().isConnected());
+}
+
+void tst_QDBusObject::construction_data()
+{
+    QTest::addColumn<QString>("service");
+    QTest::addColumn<QString>("path");
+    QTest::addColumn<bool>("isValid");
+    QTest::addColumn<bool>("exists");
+
+    QTest::newRow("null") << QString() << QString() << false << false;
+
+    QTest::newRow("invalid1") << "foo.foo1" << "" << false << false;
+    QTest::newRow("invalid2") << "foo.foo1" << "foo.bar" << false << false;
+    QTest::newRow("invalid3") << "foo.foo1" << "/foo.bar" << false << false;
+    QTest::newRow("invalid4") << "" << "/" << false << false;
+    QTest::newRow("invalid5") << "foo" << "/" << false << false;
+    QTest::newRow("invalid6") << ".foo" << "/" << false << false;
+
+    QTest::newRow("invalid7") << "org.freedesktop.DBus" << "" << false << false;
+    QTest::newRow("invalid8") << "org.freedesktop.DBus" << "foo.bar" << false << false;
+    QTest::newRow("invalid9") << "org.freedesktop.DBus" << "/foo.bar" << false << false;
+    
+    QTest::newRow("existing") << "org.freedesktop.DBus" << "/" << true << true;
+    QTest::newRow("non-existing") << "org.freedesktop.DBus" << "/foo" << true << false;
+}
+
+void tst_QDBusObject::construction()
+{
+    QDBusConnection con;        // default
+
+    QFETCH(QString, service);
+    QFETCH(QString, path);
+    QFETCH(bool, isValid);
+    //QFETCH(bool, exists);
+
+    QDBusObject o(con, service, path);
+    QCOMPARE(o.isValid(), isValid);
+
+    if (isValid) {
+        QCOMPARE(o.service(), service);
+        QCOMPARE(o.path(), path);
+    }
+    else {
+        QVERIFY(o.service().isNull());
+        QVERIFY(o.path().isNull());
+    }
+   
+    //QCOMPARE(o.exists(), exists);
+}
+
+void tst_QDBusObject::introspection_data()
+{
+    QTest::addColumn<QString>("service");
+    QTest::addColumn<QString>("path");
+    QTest::addColumn<QStringList>("interfaces");
+
+    QStringList interfaces;
+    QTest::newRow("nowhere") << QString() << QString() << interfaces;
+
+    // IMPORTANT!
+    // Keep the interface list sorted!
+    interfaces << "org.freedesktop.DBus" << DBUS_INTERFACE_INTROSPECTABLE;
+    QTest::newRow("server") << "org.freedesktop.DBus" << "/" << interfaces;
+
+    QDBusConnection con;
+    interfaces.clear();
+    interfaces << "com.trolltech.tst_qdbusobject.MyObject" << DBUS_INTERFACE_INTROSPECTABLE;    
+
+    QTest::newRow("self1") << con.baseService() << "/" << interfaces;
+    QTest::newRow("self2") << "com.trolltech.tst_qdbusobject" << "/" << interfaces;
+}
+
+void tst_QDBusObject::introspection()
+{
+    QDBusConnection con;
+
+    QFETCH(QString, service);
+    QFETCH(QString, path);
+
+    QDBusObject o(con, service, path);
+
+    if (!o.isValid())
+        QVERIFY(o.introspect().isEmpty());
+    else {
+        QFETCH(QStringList, interfaces);
+        QStringList parsed = o.interfaces();
+        parsed.sort();
+        QCOMPARE(parsed.count(), interfaces.count());
+        QCOMPARE(parsed, interfaces);
+    }
+}
+
+QTEST_MAIN(tst_QDBusObject)
+
+#include "tst_qdbusobject.moc"
+
diff --git a/test/qt/tst_qdbustype.cpp b/test/qt/tst_qdbustype.cpp
new file mode 100644 (file)
index 0000000..124e60c
--- /dev/null
@@ -0,0 +1,272 @@
+/* -*- C++ -*-
+ *
+ * Copyright (C) 2006 Trolltech AS. All rights reserved.
+ *    Author: Thiago Macieira <thiago.macieira@trolltech.com>
+ *
+ * Licensed under the Academic Free License version 2.1
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+#include <qcoreapplication.h>
+#include <QtTest/QtTest>
+
+#include <dbus/qdbus.h>
+
+class tst_QDBusType: public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void fromType_data();
+    void fromType();
+    void fromSignature_data();
+    void fromSignature();
+    void arrayOf_data();
+    void arrayOf();
+    void mapOf_data();
+    void mapOf();
+};
+
+inline QTestData &operator<<(QTestData &data, QVariant::Type t)
+{
+    return data << int(t);
+}
+
+void tst_QDBusType::fromType_data()
+{
+    fromSignature_data();
+}
+
+void tst_QDBusType:: arrayOf_data()
+{
+    fromSignature_data();
+}
+
+void tst_QDBusType::mapOf_data()
+{
+    fromSignature_data();
+}
+
+void tst_QDBusType::fromSignature_data()
+{
+    QTest::addColumn<QString>("signature");
+    QTest::addColumn<char>("type");
+    QTest::addColumn<int>("qvariantType");
+    QTest::addColumn<bool>("isValid");
+    QTest::addColumn<bool>("isBasic");
+    QTest::addColumn<bool>("isContainer");
+    QTest::addColumn<int>("subtypeCount");
+
+    QTest::newRow("null") << QString() << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("empty") << QString("") << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("invalid") << QString("~") << '\0' << QVariant::Invalid << false << false << false << 0;
+
+    // integers:
+    QTest::newRow("byte")    << "y" << 'y' << QVariant::UInt << true << true << false << 0;
+    QTest::newRow("boolean") << "b" << 'b' << QVariant::Bool << true << true << false << 0;
+    QTest::newRow("int16")   << "n" << 'n' << QVariant::Int << true << true << false << 0;
+    QTest::newRow("uint16")  << "q" << 'q' << QVariant::UInt << true << true << false << 0;
+    QTest::newRow("int32")   << "i" << 'i' << QVariant::Int << true << true << false << 0;
+    QTest::newRow("uint32")  << "u" << 'u' << QVariant::UInt << true << true << false << 0;
+    QTest::newRow("int64")   << "x" << 'x' << QVariant::LongLong << true << true << false << 0;
+    QTest::newRow("uint64")  << "t" << 't' << QVariant::ULongLong << true << true << false << 0;
+
+    // double:
+    QTest::newRow("double")  << "d" << 'd' << QVariant::Double << true << true << false << 0;
+
+    // string types:
+    QTest::newRow("string")  << "s" << 's' << QVariant::String << true << true << false << 0;
+    QTest::newRow("objpath") << "o" << 'o' << QVariant::String << true << true << false << 0;
+    QTest::newRow("signature")<<"g" << 'g' << QVariant::String << true << true << false << 0;
+
+    // variant
+    QTest::newRow("variant") << "v" << 'v' << QVariant::UserType << true << false << true << 0;
+
+    // compound types:
+    QTest::newRow("struct-empty")       << "()" << '\0' << QVariant::Invalid << false << false << false  << 0;
+    QTest::newRow("struct-invalid")     << "(~)" << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("struct-unterminated")<< "(iii" << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("struct-bad-nest")    << "(i(i)((i)i)" << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("struct1")            << "(i)" << 'r' << QVariant::List << true << false << true  << 1;
+    QTest::newRow("struct2")            << "(ii)" << 'r' << QVariant::List << true << false << true  << 2;
+
+    QTest::newRow("array-empty")        << "a" << '\0' << QVariant::Invalid << false << false << false  << 0;
+    QTest::newRow("array-invalid")      << "a~" << '\0' << QVariant::Invalid << false << false << false  << 0;
+    QTest::newRow("array-simple")       << "ab" << 'a' << QVariant::List << true << false << true  << 1;
+    QTest::newRow("bytearray")          << "ay" << 'a' << QVariant::ByteArray << true << false << true << 1;
+    QTest::newRow("stringlist")         << "as" << 'a' << QVariant::StringList << true << false << true << 1;
+    
+    QTest::newRow("map-empty")          << "e" << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("map-invalid1")       << "a{}" << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("map-invalid2")       << "a{~}" << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("map-invalid3")       << "a{e}" << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("map-invalid4")       << "a{i}" << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("map-invalid5")       << "a{(i)d}" << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("map-invalid6")       << "{}" << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("map-invalid7")       << "{i}" << '\0' << QVariant::Invalid << false << false << false << 0;
+    //QTest::newRow("map-invalid8")       << "{is}" << '\0' << QVariant::Invalid << false << false << false << 0; // this is valid when "a" is prepended
+    QTest::newRow("map-bad-nesting")    << "a{i(s}" << '\0' << QVariant::Invalid << false << false << false << 0;
+    QTest::newRow("map-ok1")            << "a{is}" << 'a' << QVariant::Map << true << false << true << 1;
+    QTest::newRow("map-ok2")            << "a{sv}" << 'a' << QVariant::Map << true << false << true << 1;
+
+    // compound of compounds:
+    QTest::newRow("struct-struct")      << "((i))" << 'r' << QVariant::List << true << false << true  << 1;
+    QTest::newRow("struct-structs")     << "((ii)d(i))" << 'r' << QVariant::List << true << false << true  << 3;
+    QTest::newRow("map-struct")         << "a{s(ii)}" << 'a' << QVariant::Map << true << false << true << 1;
+    QTest::newRow("map-stringlist")     << "a{sas}" << 'a' << QVariant::Map << true << false << true << 1;
+    QTest::newRow("map-map")            << "a{ia{sv}}" << 'a' << QVariant::Map << true << false << true << 1;
+    QTest::newRow("array-struct")       << "a(ii)" << 'a' << QVariant::List << true << false << true << 1;
+    QTest::newRow("array-array")        << "aai" << 'a' << QVariant::List << true << false << true << 1;
+    QTest::newRow("array-map")          << "aa{sv}" << 'a' << QVariant::List << true << false << true << 1;
+}
+
+void tst_QDBusType::fromType()
+{
+    QFETCH(QString, signature);
+    if (signature.length() != 1)
+        // can't transform to typecode
+        return;
+    
+    QFETCH(char, type);
+    QFETCH(int, qvariantType);
+    QFETCH(bool, isValid);
+    QFETCH(bool, isBasic);
+    QFETCH(bool, isContainer);
+
+    QDBusType t(signature.at(0).toLatin1());
+
+    QCOMPARE((char)t.dbusType(), type);
+    QCOMPARE(t.qvariantType(), QVariant::Type(qvariantType));
+    QCOMPARE(t.isValid(), isValid);
+    QCOMPARE(t.isBasic(), isBasic);
+    QCOMPARE(t.isContainer(), isContainer);
+}
+
+void tst_QDBusType::fromSignature()
+{
+    QFETCH(QString, signature);
+    QFETCH(char, type);
+    QFETCH(int, qvariantType);
+    QFETCH(bool, isValid);
+    QFETCH(bool, isBasic);
+    QFETCH(bool, isContainer);
+    QFETCH(int, subtypeCount);
+
+    QDBusType t(signature);
+
+    QCOMPARE((char)t.dbusType(), type);
+    QCOMPARE(t.qvariantType(), QVariant::Type(qvariantType));
+    QCOMPARE(t.isValid(), isValid);
+    QCOMPARE(t.isBasic(), isBasic);
+    QCOMPARE(t.isContainer(), isContainer);
+
+    if (isValid)
+        QCOMPARE(QLatin1String(t.dbusSignature()), signature);
+
+    QCOMPARE(t.subTypes().count(), subtypeCount);
+}
+
+void tst_QDBusType::arrayOf()
+{
+    QFETCH(QString, signature);
+    QFETCH(char, type);
+    QFETCH(int, qvariantType);
+    QFETCH(bool, isValid);
+    QFETCH(bool, isBasic);
+    QFETCH(bool, isContainer);
+    QFETCH(int, subtypeCount);
+
+    QDBusType arr("a" + signature.toLatin1());
+    QCOMPARE(arr.isValid(), isValid);
+    QVERIFY(!arr.isBasic());
+
+    if (isValid) {
+        QVERIFY(arr.isContainer());
+        QVERIFY(arr.isArray());
+        QCOMPARE((char)arr.dbusType(), 'a');
+        QCOMPARE(arr.subTypes().count(), 1);
+
+        // handle special cases:
+        if (type == 'y')
+            QCOMPARE(arr.qvariantType(), QVariant::ByteArray);
+        else if (type == 's' || type == 'o' || type == 'g')
+            QCOMPARE(arr.qvariantType(), QVariant::StringList);
+        else
+            QCOMPARE(arr.qvariantType(), QVariant::List);
+
+        // handle the array element now:
+        QDBusType t = arr.arrayElement();
+
+        QCOMPARE((char)t.dbusType(), type);
+        QCOMPARE(t.qvariantType(), QVariant::Type(qvariantType));
+        QCOMPARE(t.isValid(), isValid);
+        QCOMPARE(t.isBasic(), isBasic);
+        QCOMPARE(t.isContainer(), isContainer);
+
+        QCOMPARE(QLatin1String(t.dbusSignature()), signature);
+
+        QCOMPARE(t.subTypes().count(), subtypeCount);
+    }
+}
+
+void tst_QDBusType::mapOf()
+{
+    QFETCH(QString, signature);
+    QFETCH(char, type);
+    QFETCH(int, qvariantType);
+    QFETCH(bool, isValid);
+    QFETCH(bool, isBasic);
+    QFETCH(bool, isContainer);
+    QFETCH(int, subtypeCount);
+
+    QDBusType map("a{s" + signature.toLatin1() + '}');
+    QCOMPARE(map.isValid(), isValid);
+    QVERIFY(!map.isBasic());
+
+    if (isValid) {
+        QVERIFY(map.isContainer());
+        QVERIFY(map.isArray());
+        QVERIFY(map.isMap());
+        QCOMPARE((char)map.dbusType(), 'a');
+        QCOMPARE(map.subTypes().count(), 1);
+
+        // handle the array element now:
+        QDBusType dict_entry = map.arrayElement();
+        QVERIFY(dict_entry.isValid());
+        QVERIFY(dict_entry.isContainer());
+        QVERIFY(!dict_entry.isMap());
+        QVERIFY(!dict_entry.isArray());
+
+        QVERIFY(map.mapKey().isBasic());
+
+        // handle the value:
+        QDBusType t = map.mapValue();        
+
+        QCOMPARE((char)t.dbusType(), type);
+        QCOMPARE(t.qvariantType(), QVariant::Type(qvariantType));
+        QCOMPARE(t.isValid(), isValid);
+        QCOMPARE(t.isBasic(), isBasic);
+        QCOMPARE(t.isContainer(), isContainer);
+
+        QCOMPARE(QLatin1String(t.dbusSignature()), signature);
+
+        QCOMPARE(t.subTypes().count(), subtypeCount);
+    }
+}    
+
+QTEST_MAIN(tst_QDBusType)
+
+#include "tst_qdbustype.moc"
diff --git a/test/qt/tst_qdbusxmlparser.cpp b/test/qt/tst_qdbusxmlparser.cpp
new file mode 100644 (file)
index 0000000..c7337c3
--- /dev/null
@@ -0,0 +1,709 @@
+/* -*- C++ -*-
+ *
+ * Copyright (C) 2006 Trolltech AS. All rights reserved.
+ *    Author: Thiago Macieira <thiago.macieira@trolltech.com>
+ *
+ * Licensed under the Academic Free License version 2.1
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+#include <qcoreapplication.h>
+#include <qmetatype.h>
+#include <QtTest/QtTest>
+
+#include <dbus/qdbus.h>
+
+class tst_QDBusXmlParser: public QObject
+{
+    Q_OBJECT
+
+private:
+    void parsing_common(const QString&);
+
+private slots:
+    void parsing_data();
+    void parsing();
+    void parsingWithDoctype_data();
+    void parsingWithDoctype();
+
+    void objectWithContent_data();
+    void objectWithContent();
+
+    void methods_data();
+    void methods();
+    void signals__data();
+    void signals_();
+    void properties_data();
+    void properties();
+};
+
+// just to make it easier:
+typedef QDBusIntrospection::Interfaces InterfaceMap;
+typedef QDBusIntrospection::Objects ObjectMap;
+typedef QDBusIntrospection::Arguments ArgumentList;
+typedef QDBusIntrospection::Annotations AnnotationsMap;
+typedef QDBusIntrospection::Methods MethodMap;
+typedef QDBusIntrospection::Signals SignalMap;
+typedef QDBusIntrospection::Properties PropertyMap;
+
+Q_DECLARE_METATYPE(QDBusIntrospection::Method)
+Q_DECLARE_METATYPE(QDBusIntrospection::Signal)
+Q_DECLARE_METATYPE(QDBusIntrospection::Property)
+Q_DECLARE_METATYPE(MethodMap)
+Q_DECLARE_METATYPE(SignalMap)
+Q_DECLARE_METATYPE(PropertyMap)
+
+inline QDBusIntrospection::Argument arg(const char* type, const char *name = 0)
+{
+    QDBusIntrospection::Argument retval;
+    retval.type = QDBusType(type);
+    retval.name = QLatin1String(name);
+    return retval;
+}
+
+template<typename T>
+inline QMap<QString, T>& operator<<(QMap<QString, T>& map, const T& m)
+{ map.insert(m.name, m); return map; }
+
+inline const char* mapName(const MethodMap&)
+{ return "MethodMap"; }
+
+inline const char* mapName(const SignalMap&)
+{ return "SignalMap"; }
+
+inline const char* mapName(const PropertyMap&)
+{ return "PropertyMap"; }
+
+QString printable(const QDBusIntrospection::Method& m)
+{
+    QString result = m.name + "(";
+    foreach (QDBusIntrospection::Argument arg, m.inputArgs)
+        result += QString("in %1 %2, ")
+        .arg(arg.type.toString(QDBusType::ConventionalNames))
+        .arg(arg.name);
+    foreach (QDBusIntrospection::Argument arg, m.outputArgs)
+        result += QString("out %1 %2, ")
+        .arg(arg.type.toString(QDBusType::ConventionalNames))
+        .arg(arg.name);
+    AnnotationsMap::const_iterator it = m.annotations.begin();
+    for ( ; it != m.annotations.end(); ++it)
+        result += QString("%1 \"%2\", ").arg(it.key()).arg(it.value());
+
+    if (result.length() > 1)
+        result.truncate(result.length() - 2);
+    result += ")";
+    return result;
+}    
+
+QString printable(const QDBusIntrospection::Signal& s)
+{
+    QString result = s.name + "(";
+    foreach (QDBusIntrospection::Argument arg, s.outputArgs)
+        result += QString("out %1 %2, ")
+        .arg(arg.type.toString(QDBusType::ConventionalNames))
+        .arg(arg.name);
+    AnnotationsMap::const_iterator it = s.annotations.begin();
+    for ( ; it != s.annotations.end(); ++it)
+        result += QString("%1 \"%2\", ").arg(it.key()).arg(it.value());
+
+    if (result.length() > 1)
+        result.truncate(result.length() - 2);
+    result += ")";
+    return result;
+}    
+
+QString printable(const QDBusIntrospection::Property& p)
+{
+    QString result;
+    if (p.access == QDBusIntrospection::Property::Read)
+        result = "read %1 %2, ";
+    else if (p.access == QDBusIntrospection::Property::Write)
+        result = "write %1 %2, ";
+    else
+        result = "readwrite %1 %2, ";
+    result = result.arg(p.type.toString(QDBusType::ConventionalNames)).arg(p.name);
+    
+    AnnotationsMap::const_iterator it = p.annotations.begin();
+    for ( ; it != p.annotations.end(); ++it)
+        result += QString("%1 \"%2\", ").arg(it.key()).arg(it.value());
+
+    if (result.length() > 1)
+        result.truncate(result.length() - 2);
+    return result;
+}    
+
+template<typename T>
+char* printableMap(const QMap<QString, T>& map)
+{
+    QString contents = "\n";
+    typename QMap<QString, T>::const_iterator it = map.begin();
+    for ( ; it != map.end(); ++it) {
+        if (it.key() != it.value().name)
+            contents += it.value().name + ":";
+        contents += printable(it.value());
+        contents += ";\n";
+    }
+
+    QString result("%1(size = %2): {%3}");
+    return qstrdup(qPrintable(result
+                              .arg(mapName(map))
+                              .arg(map.size())
+                              .arg(contents)));
+}
+
+namespace QTest {
+    template<>
+    inline char* toString(const MethodMap& map)
+    {
+        return printableMap(map);
+    }
+
+    template<>
+    inline char* toString(const SignalMap& map)
+    {
+        return printableMap(map);
+    }
+
+    template<>
+    inline char* toString(const PropertyMap& map)
+    {
+        return printableMap(map);
+    }
+}
+
+void tst_QDBusXmlParser::parsing_data()
+{
+    QTest::addColumn<QString>("xmlData");
+    QTest::addColumn<int>("interfaceCount");
+    QTest::addColumn<int>("objectCount");
+
+    QTest::newRow("null") << QString() << 0 << 0;
+    QTest::newRow("empty") << QString("") << 0 << 0;
+    
+    QTest::newRow("junk") << "<junk/>" << 0 << 0;
+    QTest::newRow("interface-inside-junk") << "<junk><interface name=\"iface.iface1\" /></junk>"
+                                           << 0 << 0;
+    QTest::newRow("object-inside-junk") << "<junk><node name=\"obj1\" /></junk>"
+                                        << 0 << 0;
+
+    QTest::newRow("zero-interfaces") << "<node/>" << 0 << 0;
+    QTest::newRow("one-interface") << "<node><interface name=\"iface.iface1\" /></node>" << 1 << 0;
+
+    
+    QTest::newRow("two-interfaces") << "<node><interface name=\"iface.iface1\" />"
+                                       "<interface name=\"iface.iface2\"></node>"
+                                    << 2 << 0;        
+
+
+    QTest::newRow("one-object") << "<node><node name=\"obj1\"/></node>" << 0 << 1;
+    QTest::newRow("two-objects") << "<node><node name=\"obj1\"/><node name=\"obj2\"></node>" << 0 << 2;
+
+    QTest::newRow("i1o1") << "<node><interface name=\"iface.iface1\"><node name=\"obj1\"></node>" << 1 << 1;
+
+}
+
+void tst_QDBusXmlParser::parsing_common(const QString &xmlData)
+{
+    QDBusIntrospection::ObjectTree obj =
+        QDBusIntrospection::parseObjectTree(xmlData, "local.testing", "/");
+    QFETCH(int, interfaceCount);
+    QFETCH(int, objectCount);
+    QCOMPARE(obj.interfaces.count(), interfaceCount);
+    QCOMPARE(obj.childObjects.count(), objectCount);
+
+    // also verify the naming
+    int i = 0;
+    foreach (QString name, obj.interfaces)
+        QCOMPARE(name, QString("iface.iface%1").arg(++i));
+
+    i = 0;
+    foreach (QString name, obj.childObjects)
+        QCOMPARE(name, QString("obj%1").arg(++i));
+}
+
+void tst_QDBusXmlParser::parsing()
+{
+    QFETCH(QString, xmlData);
+
+    parsing_common(xmlData);
+}
+
+void tst_QDBusXmlParser::parsingWithDoctype_data()
+{
+    parsing_data();
+}
+
+void tst_QDBusXmlParser::parsingWithDoctype()
+{
+    QString docType = "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
+                      "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n";
+    QFETCH(QString, xmlData);
+
+    parsing_common(docType + xmlData);
+}    
+
+void tst_QDBusXmlParser::objectWithContent_data()
+{
+    QTest::addColumn<QString>("xmlData");
+    QTest::addColumn<QString>("probedObject");
+    QTest::addColumn<int>("interfaceCount");
+    QTest::addColumn<int>("objectCount");
+
+    QTest::newRow("zero") << "<node><node name=\"obj\"/></node>" << "obj" << 0 << 0;
+
+    QString xmlData = "<node><node name=\"obj\">"
+                      "<interface name=\"iface.iface1\" />"
+                      "</node></node>";
+    QTest::newRow("one-interface") << xmlData << "obj" << 1 << 0;
+    QTest::newRow("one-interface2") << xmlData << "obj2" << 0 << 0;
+
+    xmlData = "<node><node name=\"obj\">"
+              "<interface name=\"iface.iface1\" />"
+              "<interface name=\"iface.iface2\" />"
+              "</node></node>";
+    QTest::newRow("two-interfaces") << xmlData << "obj" << 2 << 0;
+    QTest::newRow("two-interfaces2") << xmlData << "obj2" << 0 << 0;
+
+    xmlData = "<node><node name=\"obj\">"
+              "<interface name=\"iface.iface1\" />"
+              "<interface name=\"iface.iface2\" />"
+              "</node><node name=\"obj2\">"
+              "<interface name=\"iface.iface1\" />"
+              "</node></node>";
+    QTest::newRow("two-nodes-two-interfaces") << xmlData << "obj" << 2 << 0;
+    QTest::newRow("two-nodes-one-interface") << xmlData << "obj2" << 1 << 0;
+
+    xmlData = "<node><node name=\"obj\">"
+              "<node name=\"obj1\" />"
+              "</node></node>";
+    QTest::newRow("one-object") << xmlData << "obj" << 0 << 1;
+    QTest::newRow("one-object2") << xmlData << "obj2" << 0 << 0;
+
+    xmlData = "<node><node name=\"obj\">"
+              "<node name=\"obj1\" />"
+              "<node name=\"obj2\" />"
+              "</node></node>";
+    QTest::newRow("two-objects") << xmlData << "obj" << 0 << 2;
+    QTest::newRow("two-objects2") << xmlData << "obj2" << 0 << 0;
+
+    xmlData = "<node><node name=\"obj\">"
+              "<node name=\"obj1\" />"
+              "<node name=\"obj2\" />"
+              "</node><node name=\"obj2\">"
+              "<node name=\"obj1\" />"
+              "</node></node>";
+    QTest::newRow("two-nodes-two-objects") << xmlData << "obj" << 0 << 2;
+    QTest::newRow("two-nodes-one-object") << xmlData << "obj2" << 0 << 1;
+}
+
+void tst_QDBusXmlParser::objectWithContent()
+{
+    QFETCH(QString, xmlData);
+    QFETCH(QString, probedObject);
+
+    QDBusIntrospection::ObjectTree tree =
+        QDBusIntrospection::parseObjectTree(xmlData, "local.testing", "/");
+
+    const ObjectMap &om = tree.childObjectData;
+
+    if (om.contains(probedObject)) {
+        const QSharedDataPointer<QDBusIntrospection::ObjectTree>& obj = om.value(probedObject);
+        QVERIFY(obj != 0);
+    
+        QFETCH(int, interfaceCount);
+        QFETCH(int, objectCount);
+
+        QCOMPARE(obj->interfaces.count(), interfaceCount);
+        QCOMPARE(obj->childObjects.count(), objectCount);
+
+        // verify the object names
+        int i = 0;
+        foreach (QString name, obj->interfaces)
+            QCOMPARE(name, QString("iface.iface%1").arg(++i));
+
+        i = 0;
+        foreach (QString name, obj->childObjects)
+            QCOMPARE(name, QString("obj%1").arg(++i));
+    }
+}
+
+void tst_QDBusXmlParser::methods_data()
+{
+    QTest::addColumn<QString>("xmlDataFragment");
+    QTest::addColumn<MethodMap>("methodMap");
+
+    MethodMap map;
+    QTest::newRow("no-methods") << QString() << map;
+
+    // one method without arguments
+    QDBusIntrospection::Method method;
+    method.name = "Foo";
+    map << method;
+    QTest::newRow("one-method") << "<method name=\"Foo\"/>" << map;
+
+    // add another method without arguments
+    method.name = "Bar";
+    map << method;
+    QTest::newRow("two-methods") << "<method name=\"Foo\"/>"
+                                    "<method name=\"Bar\"/>"
+                                 << map;
+
+    // invert the order of the XML declaration
+    QTest::newRow("two-methods-inverse") << "<method name=\"Bar\"/>"
+                                            "<method name=\"Foo\"/>"
+                                         << map;
+
+    // add a third, with annotations
+    method.name = "Baz";
+    method.annotations.insert("foo.testing", "nothing to see here");
+    map << method;
+    QTest::newRow("method-with-annotation") <<
+        "<method name=\"Foo\"/>"
+        "<method name=\"Bar\"/>"
+        "<method name=\"Baz\"><annotation name=\"foo.testing\" value=\"nothing to see here\"></method>"
+                                            << map;
+
+    // arguments
+    map.clear();
+    method.annotations.clear();
+
+    method.name = "Method";
+    method.inputArgs << arg("s");
+    map << method;
+    QTest::newRow("one-in") <<
+        "<method name=\"Method\">"
+        "<arg type=\"s\" direction=\"in\"/>"
+        "</method>" << map;
+
+    // two arguments
+    method.inputArgs << arg("v");
+    map.clear();
+    map << method;
+    QTest::newRow("two-in") <<
+        "<method name=\"Method\">"
+        "<arg type=\"s\" direction=\"in\"/>"
+        "<arg type=\"v\" direction=\"in\"/>"
+        "</method>" << map;
+
+    // one invalid arg
+    QTest::newRow("two-in-one-invalid") <<
+        "<method name=\"Method\">"
+        "<arg type=\"s\" direction=\"in\"/>"
+        "<arg type=\"~\" name=\"invalid\" direction=\"in\"/>" // this line should be ignored
+        "<arg type=\"v\" direction=\"in\"/>"
+        "</method>" << map;
+
+    // one out argument
+    method.inputArgs.clear();
+    method.outputArgs << arg("s");
+    map.clear();
+    map << method;
+    QTest::newRow("one-out") <<
+        "<method name=\"Method\">"
+        "<arg type=\"s\" direction=\"out\"/>"
+        "</method>" << map;
+
+    // two in and one out
+    method.inputArgs << arg("s") << arg("v");
+    map.clear();
+    map << method;
+    QTest::newRow("two-in-one-out") <<
+        "<method name=\"Method\">"
+        "<arg type=\"s\" direction=\"in\"/>"
+        "<arg type=\"v\" direction=\"in\"/>"
+        "<arg type=\"s\" direction=\"out\"/>"
+        "</method>" << map;
+
+    // let's try an arg with name
+    method.outputArgs.clear();
+    method.inputArgs.clear();
+    method.inputArgs << arg("s", "foo");
+    map.clear();
+    map << method;
+    QTest::newRow("one-in-with-name") <<
+        "<method name=\"Method\">"
+        "<arg type=\"s\" name=\"foo\" direction=\"in\"/>"
+        "</method>" << map;
+
+    // two args with name
+    method.inputArgs << arg("i", "bar");
+    map.clear();
+    map << method;
+    QTest::newRow("two-in-with-name") <<
+        "<method name=\"Method\">"
+        "<arg type=\"s\" name=\"foo\" direction=\"in\"/>"
+        "<arg type=\"i\" name=\"bar\" direction=\"in\"/>"
+        "</method>" << map;
+
+    // one complex
+    map.clear();
+    method = QDBusIntrospection::Method();
+
+    // Method1(in STRING arg1, in BYTE arg2, out ARRAY of STRING)
+    method.inputArgs << arg("s", "arg1") << arg("y", "arg2");
+    method.outputArgs << arg("as");
+    method.name = "Method1";
+    map << method;
+
+    // Method2(in ARRAY of DICT_ENTRY of (STRING,VARIANT) variantMap, in UINT32 index,
+    //         out STRING key, out VARIANT value)
+    // with annotation "foo.equivalent":"QVariantMap"
+    method = QDBusIntrospection::Method();
+    method.inputArgs << arg("a{sv}", "variantMap") << arg("u", "index");
+    method.outputArgs << arg("s", "key") << arg("v", "value");
+    method.annotations.insert("foo.equivalent", "QVariantMap");
+    method.name = "Method2";
+    map << method;
+
+    QTest::newRow("complex") <<
+        "<method name=\"Method1\">"
+        "<arg name=\"arg1\" type=\"s\" direction=\"in\"/>"
+        "<arg name=\"arg2\" type=\"y\" direction=\"in\"/>"
+        "<arg type=\"as\" direction=\"out\"/>"
+        "</method>"
+        "<method name=\"Method2\">"
+        "<arg name=\"variantMap\" type=\"a{sv}\" direction=\"in\"/>"
+        "<arg name=\"index\" type=\"u\" direction=\"in\"/>"
+        "<arg name=\"key\" type=\"s\" direction=\"out\"/>"
+        "<arg name=\"value\" type=\"v\" direction=\"out\"/>"
+        "<annotation name=\"foo.equivalent\" value=\"QVariantMap\"/>"
+        "</method>" << map;
+}
+
+void tst_QDBusXmlParser::methods()
+{
+    QString xmlHeader = "<node>"
+                        "<interface name=\"iface.iface1\">",
+            xmlFooter = "</interface>"
+                        "</node>";
+
+    QFETCH(QString, xmlDataFragment);
+
+    QDBusIntrospection::Interface iface =
+        QDBusIntrospection::parseInterface(xmlHeader + xmlDataFragment + xmlFooter);
+
+    QCOMPARE(iface.name, QString("iface.iface1"));
+
+    QFETCH(MethodMap, methodMap);
+    MethodMap parsedMap = iface.methods;
+
+    QCOMPARE(methodMap.count(), parsedMap.count());
+    QCOMPARE(methodMap, parsedMap);
+}             
+
+void tst_QDBusXmlParser::signals__data()
+{
+    QTest::addColumn<QString>("xmlDataFragment");
+    QTest::addColumn<SignalMap>("signalMap");
+
+    SignalMap map;
+    QTest::newRow("no-signals") << QString() << map;
+
+    // one signal without arguments
+    QDBusIntrospection::Signal signal;
+    signal.name = "Foo";
+    map << signal;
+    QTest::newRow("one-signal") << "<signal name=\"Foo\"/>" << map;
+
+    // add another signal without arguments
+    signal.name = "Bar";
+    map << signal;
+    QTest::newRow("two-signals") << "<signal name=\"Foo\"/>"
+                                    "<signal name=\"Bar\"/>"
+                                 << map;
+
+    // invert the order of the XML declaration
+    QTest::newRow("two-signals-inverse") << "<signal name=\"Bar\"/>"
+                                            "<signal name=\"Foo\"/>"
+                                         << map;
+
+    // add a third, with annotations
+    signal.name = "Baz";
+    signal.annotations.insert("foo.testing", "nothing to see here");
+    map << signal;
+    QTest::newRow("signal-with-annotation") <<
+        "<signal name=\"Foo\"/>"
+        "<signal name=\"Bar\"/>"
+        "<signal name=\"Baz\"><annotation name=\"foo.testing\" value=\"nothing to see here\"></signal>"
+                                            << map;
+
+    // one out argument
+    map.clear();
+    signal.annotations.clear();
+    signal.outputArgs << arg("s");
+    signal.name = "Signal";
+    map.clear();
+    map << signal;
+    QTest::newRow("one-out") <<
+        "<signal name=\"Signal\">"
+        "<arg type=\"s\" direction=\"out\"/>"
+        "</signal>" << map;
+
+    // without saying which direction it is
+    QTest::newRow("one-out-no-direction") <<
+        "<signal name=\"Signal\">"
+        "<arg type=\"s\"/>"
+        "</signal>" << map;    
+
+    // two args with name
+    signal.outputArgs << arg("i", "bar");
+    map.clear();
+    map << signal;
+    QTest::newRow("two-out-with-name") <<
+        "<signal name=\"Signal\">"
+        "<arg type=\"s\" direction=\"out\"/>"
+        "<arg type=\"i\" name=\"bar\"/>"
+        "</signal>" << map;
+
+    // one complex
+    map.clear();
+    signal = QDBusIntrospection::Signal();
+
+    // Signal1(out ARRAY of STRING)
+    signal.outputArgs << arg("as");
+    signal.name = "Signal1";
+    map << signal;
+
+    // Signal2(out STRING key, out VARIANT value)
+    // with annotation "foo.equivalent":"QVariantMap"
+    signal = QDBusIntrospection::Signal();
+    signal.outputArgs << arg("s", "key") << arg("v", "value");
+    signal.annotations.insert("foo.equivalent", "QVariantMap");
+    signal.name = "Signal2";
+    map << signal;
+
+    QTest::newRow("complex") <<
+        "<signal name=\"Signal1\">"
+        "<arg type=\"as\" direction=\"out\"/>"
+        "</signal>"
+        "<signal name=\"Signal2\">"
+        "<arg name=\"key\" type=\"s\" direction=\"out\"/>"
+        "<arg name=\"value\" type=\"v\" direction=\"out\"/>"
+        "<annotation name=\"foo.equivalent\" value=\"QVariantMap\"/>"
+        "</signal>" << map;
+}
+
+void tst_QDBusXmlParser::signals_()
+{
+    QString xmlHeader = "<node>"
+                        "<interface name=\"iface.iface1\">",
+            xmlFooter = "</interface>"
+                        "</node>";
+
+    QFETCH(QString, xmlDataFragment);
+
+    QDBusIntrospection::Interface iface =
+        QDBusIntrospection::parseInterface(xmlHeader + xmlDataFragment + xmlFooter);
+
+    QCOMPARE(iface.name, QString("iface.iface1"));
+
+    QFETCH(SignalMap, signalMap);
+    SignalMap parsedMap = iface.signals_;
+
+    QCOMPARE(signalMap.count(), parsedMap.count());
+    QCOMPARE(signalMap, parsedMap);
+}
+
+void tst_QDBusXmlParser::properties_data()
+{
+    QTest::addColumn<QString>("xmlDataFragment");
+    QTest::addColumn<PropertyMap>("propertyMap");
+
+    PropertyMap map;
+    QTest::newRow("no-signals") << QString() << map;
+
+    // one readable signal
+    QDBusIntrospection::Property prop;
+    prop.name = "foo";
+    prop.type = QDBusType("s");
+    prop.access = QDBusIntrospection::Property::Read;
+    map << prop;
+    QTest::newRow("one-readable") << "<property name=\"foo\" type=\"s\" access=\"read\"/>" << map;
+
+    // one writable signal
+    prop.access = QDBusIntrospection::Property::Write;
+    map.clear();
+    map << prop;
+    QTest::newRow("one-writable") << "<property name=\"foo\" type=\"s\" access=\"write\"/>" << map;
+
+    // one read- & writable signal
+    prop.access = QDBusIntrospection::Property::ReadWrite;
+    map.clear();
+    map << prop;
+    QTest::newRow("one-read-writable") << "<property name=\"foo\" type=\"s\" access=\"readwrite\"/>"
+                                       << map;
+
+    // two, mixed properties
+    prop.name = "bar";
+    prop.type = QDBusType("i");
+    prop.access = QDBusIntrospection::Property::Read;
+    map << prop;
+    QTest::newRow("two") <<
+        "<property name=\"foo\" type=\"s\" access=\"readwrite\"/>"
+        "<property name=\"bar\" type=\"i\" access=\"read\"/>" << map;
+
+    // invert the order of the declaration
+    QTest::newRow("two") <<
+        "<property name=\"bar\" type=\"i\" access=\"read\"/>"
+        "<property name=\"foo\" type=\"s\" access=\"readwrite\"/>" << map;
+
+    // add a third with annotations
+    prop.name = "baz";
+    prop.type = QDBusType("as");
+    prop.access = QDBusIntrospection::Property::Write;
+    prop.annotations.insert("foo.annotation", "Hello, World");
+    prop.annotations.insert("foo.annotation2", "Goodbye, World");
+    map << prop;
+    QTest::newRow("complex") <<
+        "<property name=\"bar\" type=\"i\" access=\"read\"/>"
+        "<property name=\"baz\" type=\"as\" access=\"write\">"
+        "<annotation name=\"foo.annotation\" value=\"Hello, World\" />"
+        "<annotation name=\"foo.annotation2\" value=\"Goodbye, World\" />"
+        "<property name=\"foo\" type=\"s\" access=\"readwrite\"/>" << map;
+
+    // and now change the order
+    QTest::newRow("complex2") <<
+        "<property name=\"baz\" type=\"as\" access=\"write\">"
+        "<annotation name=\"foo.annotation2\" value=\"Goodbye, World\" />"
+        "<annotation name=\"foo.annotation\" value=\"Hello, World\" />"
+        "<property name=\"bar\" type=\"i\" access=\"read\"/>"
+        "<property name=\"foo\" type=\"s\" access=\"readwrite\"/>" << map;
+}
+
+void tst_QDBusXmlParser::properties()
+{
+    QString xmlHeader = "<node>"
+                        "<interface name=\"iface.iface1\">",
+            xmlFooter = "</interface>"
+                        "</node>";
+
+    QFETCH(QString, xmlDataFragment);
+
+    QDBusIntrospection::Interface iface =
+        QDBusIntrospection::parseInterface(xmlHeader + xmlDataFragment + xmlFooter);
+
+    QCOMPARE(iface.name, QString("iface.iface1"));
+
+    QFETCH(PropertyMap, propertyMap);
+    PropertyMap parsedMap = iface.properties;
+
+    QCOMPARE(propertyMap.count(), parsedMap.count());
+    QCOMPARE(propertyMap, parsedMap);
+}
+
+QTEST_MAIN(tst_QDBusXmlParser)
+
+#include "tst_qdbusxmlparser.moc"