/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2016 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qcoreapplication.h>
#include <qdebug.h>
#include <qsharedpointer.h>

#include <QtTest/QtTest>

#include <QtDBus>

#include "interface.h"
#include "pinger_interface.h"

static const char serviceName[] = "org.qtproject.autotests.qpinger";
static const char objectPath[] = "/org/qtproject/qpinger";
static const char *interfaceName = serviceName;

typedef QSharedPointer<org::qtproject::QtDBus::Pinger> Pinger;

class tst_QDBusAbstractInterface: public QObject
{
    Q_OBJECT
    Interface targetObj;
    QString peerAddress;

    Pinger getPinger(QString service = "", const QString &path = "/")
    {
        QDBusConnection con = QDBusConnection::sessionBus();
        if (!con.isConnected())
            return Pinger();
        if (service.isEmpty() && !service.isNull())
            service = con.baseService();
        return Pinger::create(service, path, con);
    }

    Pinger getPingerPeer(const QString &path = "/", const QString &service = "")
    {
        QDBusConnection con = QDBusConnection("peer");
        if (!con.isConnected())
            return Pinger();
        return Pinger::create(service, path, con);
    }

    void resetServer()
    {
        QDBusMessage req = QDBusMessage::createMethodCall(serviceName, objectPath, interfaceName, "reset");
        QDBusConnection::sessionBus().send(req);
    }

public:
    tst_QDBusAbstractInterface();

private slots:
    void initTestCase();
    void cleanupTestCase();

    void init();
    void cleanup();

    void makeVoidCall();
    void makeStringCall();
    void makeComplexCall();
    void makeMultiOutCall();

    void makeVoidCallPeer();
    void makeStringCallPeer();
    void makeComplexCallPeer();
    void makeMultiOutCallPeer();

    void makeAsyncVoidCall();
    void makeAsyncStringCall();
    void makeAsyncComplexCall();
    void makeAsyncMultiOutCall();

    void makeAsyncVoidCallPeer();
    void makeAsyncStringCallPeer();
    void makeAsyncComplexCallPeer();
    void makeAsyncMultiOutCallPeer();

    void callWithTimeout();

    void stringPropRead();
    void stringPropWrite();
    void variantPropRead();
    void variantPropWrite();
    void complexPropRead();
    void complexPropWrite();

    void stringPropReadPeer();
    void stringPropWritePeer();
    void variantPropReadPeer();
    void variantPropWritePeer();
    void complexPropReadPeer();
    void complexPropWritePeer();

    void stringPropDirectRead();
    void stringPropDirectWrite();
    void variantPropDirectRead();
    void variantPropDirectWrite();
    void complexPropDirectRead();
    void complexPropDirectWrite();

    void stringPropDirectReadPeer();
    void stringPropDirectWritePeer();
    void variantPropDirectReadPeer();
    void variantPropDirectWritePeer();
    void complexPropDirectReadPeer();
    void complexPropDirectWritePeer();

    void getVoidSignal_data();
    void getVoidSignal();
    void getStringSignal_data();
    void getStringSignal();
    void getComplexSignal_data();
    void getComplexSignal();

    void getVoidSignalPeer_data();
    void getVoidSignalPeer();
    void getStringSignalPeer_data();
    void getStringSignalPeer();
    void getComplexSignalPeer_data();
    void getComplexSignalPeer();

    void followSignal();

    void connectDisconnect_data();
    void connectDisconnect();
    void connectDisconnectPeer_data();
    void connectDisconnectPeer();

    void createErrors_data();
    void createErrors();

    void createErrorsPeer_data();
    void createErrorsPeer();

    void callErrors_data();
    void callErrors();
    void asyncCallErrors_data();
    void asyncCallErrors();

    void callErrorsPeer_data();
    void callErrorsPeer();
    void asyncCallErrorsPeer_data();
    void asyncCallErrorsPeer();

    void propertyReadErrors_data();
    void propertyReadErrors();
    void propertyWriteErrors_data();
    void propertyWriteErrors();
    void directPropertyReadErrors_data();
    void directPropertyReadErrors();
    void directPropertyWriteErrors_data();
    void directPropertyWriteErrors();

    void propertyReadErrorsPeer_data();
    void propertyReadErrorsPeer();
    void propertyWriteErrorsPeer_data();
    void propertyWriteErrorsPeer();
    void directPropertyReadErrorsPeer_data();
    void directPropertyReadErrorsPeer();
    void directPropertyWriteErrorsPeer_data();
    void directPropertyWriteErrorsPeer();

    void validity_data();
    void validity();

private:
    QProcess proc;
};

class SignalReceiver : public QObject
{
    Q_OBJECT
public:
    int callCount;
    SignalReceiver() : callCount(0) {}
public slots:
    void receive() { ++callCount; }
};

tst_QDBusAbstractInterface::tst_QDBusAbstractInterface()
{
    // register the meta types
    qDBusRegisterMetaType<RegisteredType>();
    qRegisterMetaType<UnregisteredType>();
}

void tst_QDBusAbstractInterface::initTestCase()
{
    // enable debugging temporarily:
    //putenv("QDBUS_DEBUG=1");

    // register the object
    QDBusConnection con = QDBusConnection::sessionBus();
    QVERIFY(con.isConnected());
    con.registerObject("/", &targetObj, QDBusConnection::ExportScriptableContents);

    // verify service isn't registered by something else
    // (e.g. a left over qpinger from a previous test run)
    QVERIFY(!con.interface()->isServiceRegistered(serviceName));

    // start peer server
#ifdef Q_OS_WIN
#  define EXE ".exe"
#else
#  define EXE ""
#endif
    proc.setProcessChannelMode(QProcess::ForwardedErrorChannel);
    proc.start(QFINDTESTDATA("qpinger/qpinger" EXE));
    QVERIFY2(proc.waitForStarted(), qPrintable(proc.errorString()));
    QVERIFY(proc.waitForReadyRead());

    // verify service is now registered
    QTRY_VERIFY(con.interface()->isServiceRegistered(serviceName));

    // get peer server address
    QDBusMessage req = QDBusMessage::createMethodCall(serviceName, objectPath, interfaceName, "address");
    QDBusMessage rpl = con.call(req);
    QCOMPARE(rpl.type(), QDBusMessage::ReplyMessage);
    peerAddress = rpl.arguments().at(0).toString();
}

void tst_QDBusAbstractInterface::cleanupTestCase()
{
    QDBusMessage msg = QDBusMessage::createMethodCall(serviceName, objectPath, interfaceName, "quit");
    QDBusConnection::sessionBus().call(msg);
    proc.waitForFinished(200);
    proc.close();
}

void tst_QDBusAbstractInterface::init()
{
    QDBusConnection con = QDBusConnection::sessionBus();
    QVERIFY(con.isConnected());

    // connect to peer server
    QDBusConnection peercon = QDBusConnection::connectToPeer(peerAddress, "peer");
    QVERIFY(peercon.isConnected());

    QDBusMessage req2 = QDBusMessage::createMethodCall(serviceName, objectPath, interfaceName, "waitForConnected");
    QDBusMessage rpl2 = con.call(req2);
    QVERIFY2(rpl2.type() == QDBusMessage::ReplyMessage, rpl2.errorMessage().toLatin1());
}

void tst_QDBusAbstractInterface::cleanup()
{
    QDBusConnection::disconnectFromPeer("peer");

    // Reset the object exported by this process
    targetObj.m_stringProp = QString();
    targetObj.m_variantProp = QDBusVariant();
    targetObj.m_complexProp = RegisteredType();

    QDBusMessage resetCall = QDBusMessage::createMethodCall(serviceName, objectPath, interfaceName, "reset");
    QCOMPARE(QDBusConnection::sessionBus().call(resetCall).type(), QDBusMessage::ReplyMessage);
}

void tst_QDBusAbstractInterface::makeVoidCall()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusReply<void> r = p->voidMethod();
    QVERIFY(r.isValid());
}

void tst_QDBusAbstractInterface::makeStringCall()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusReply<QString> r = p->stringMethod();
    QVERIFY(r.isValid());
    QCOMPARE(r.value(), targetObj.stringMethod());
}

static QHash<QString, QVariant> complexMethodArgs()
{
    QHash<QString, QVariant> args;
    args.insert("arg1", "Hello world");
    args.insert("arg2", 12345);
    return args;
}

void tst_QDBusAbstractInterface::makeComplexCall()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusReply<RegisteredType> r = p->complexMethod(complexMethodArgs());
    QVERIFY(r.isValid());
    QCOMPARE(r.value(), targetObj.complexMethod(complexMethodArgs()));
}

void tst_QDBusAbstractInterface::makeMultiOutCall()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    int value;
    QDBusReply<QString> r = p->multiOutMethod(value);
    QVERIFY(r.isValid());

    int expectedValue;
    QCOMPARE(r.value(), targetObj.multiOutMethod(expectedValue));
    QCOMPARE(value, expectedValue);
}

void tst_QDBusAbstractInterface::makeVoidCallPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusReply<void> r = p->voidMethod();
    QVERIFY(r.isValid());
}

void tst_QDBusAbstractInterface::makeStringCallPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusReply<QString> r = p->stringMethod();
    QVERIFY(r.isValid());
    QCOMPARE(r.value(), targetObj.stringMethod());
}

void tst_QDBusAbstractInterface::makeComplexCallPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusReply<RegisteredType> r = p->complexMethod(complexMethodArgs());
    QVERIFY(r.isValid());
    QCOMPARE(r.value(), targetObj.complexMethod(complexMethodArgs()));
}

void tst_QDBusAbstractInterface::makeMultiOutCallPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");

    int value;
    QDBusReply<QString> r = p->multiOutMethod(value);
    QVERIFY(r.isValid());

    int expectedValue;
    QCOMPARE(r.value(), targetObj.multiOutMethod(expectedValue));
    QCOMPARE(value, expectedValue);
}

void tst_QDBusAbstractInterface::makeAsyncVoidCall()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusPendingReply<void> r = p->voidMethod();
    r.waitForFinished();
    QVERIFY(r.isValid());
}

void tst_QDBusAbstractInterface::makeAsyncStringCall()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusPendingReply<QString> r = p->stringMethod();
    r.waitForFinished();
    QVERIFY(r.isValid());
    QCOMPARE(r.value(), targetObj.stringMethod());
}

void tst_QDBusAbstractInterface::makeAsyncComplexCall()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusPendingReply<RegisteredType> r = p->complexMethod(complexMethodArgs());
    r.waitForFinished();
    QVERIFY(r.isValid());
    QCOMPARE(r.value(), targetObj.complexMethod(complexMethodArgs()));
}

void tst_QDBusAbstractInterface::makeAsyncMultiOutCall()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusPendingReply<QString, int> r = p->multiOutMethod();
    r.waitForFinished();
    QVERIFY(r.isValid());

    int expectedValue;
    QCOMPARE(r.value(), targetObj.multiOutMethod(expectedValue));
    QCOMPARE(r.argumentAt<1>(), expectedValue);
}

void tst_QDBusAbstractInterface::makeAsyncVoidCallPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusPendingReply<void> r = p->voidMethod();
    r.waitForFinished();
    QVERIFY(r.isValid());
}
void tst_QDBusAbstractInterface::makeAsyncStringCallPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusMessage reply = p->call(QDBus::BlockWithGui, QLatin1String("voidMethod"));
    QCOMPARE(reply.type(), QDBusMessage::ReplyMessage);

    QDBusPendingReply<QString> r = p->stringMethod();
    r.waitForFinished();
    QVERIFY(r.isValid());
    QCOMPARE(r.value(), targetObj.stringMethod());
}

void tst_QDBusAbstractInterface::makeAsyncComplexCallPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusPendingReply<RegisteredType> r = p->complexMethod(complexMethodArgs());
    r.waitForFinished();
    QVERIFY(r.isValid());
    QCOMPARE(r.value(), targetObj.complexMethod(complexMethodArgs()));
}

void tst_QDBusAbstractInterface::makeAsyncMultiOutCallPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusPendingReply<QString, int> r = p->multiOutMethod();
    r.waitForFinished();
    QVERIFY(r.isValid());

    int expectedValue;
    QCOMPARE(r.value(), targetObj.multiOutMethod(expectedValue));
    QCOMPARE(r.argumentAt<1>(), expectedValue);
    QCoreApplication::instance()->processEvents();
}

static const char server_serviceName[] = "org.qtproject.autotests.dbusserver";
static const char server_objectPath[] = "/org/qtproject/server";
static const char server_interfaceName[] = "org.qtproject.QtDBus.Pinger";

class DBusServerThread : public QThread
{
public:
    DBusServerThread() {
        start();
        m_ready.acquire();
    }
    ~DBusServerThread() {
        quit();
        wait();
    }

    void run()
    {
        QDBusConnection con = QDBusConnection::connectToBus(QDBusConnection::SessionBus, "ThreadConnection");
        if (!con.isConnected())
            qWarning("Error registering to DBus");
        if (!con.registerService(server_serviceName))
            qWarning("Error registering service name");
        Interface targetObj;
        con.registerObject(server_objectPath, &targetObj, QDBusConnection::ExportScriptableContents);
        m_ready.release();
        exec();

        QDBusConnection::disconnectFromBus( con.name() );
    }
private:
    QSemaphore m_ready;
};

void tst_QDBusAbstractInterface::callWithTimeout()
{
    QDBusConnection con = QDBusConnection::sessionBus();
    QVERIFY2(con.isConnected(), "Not connected to D-Bus");

    DBusServerThread serverThread;

    QDBusMessage msg = QDBusMessage::createMethodCall(server_serviceName,
                                                      server_objectPath, server_interfaceName, "sleepMethod");
    msg << 100; // sleep 100 ms

    {
       // Call with no timeout -> works
        QDBusMessage reply = con.call(msg);
        QCOMPARE((int)reply.type(), (int)QDBusMessage::ReplyMessage);
        QCOMPARE(reply.arguments().at(0).toInt(), 42);
    }

    {
        // Call with 1 msec timeout -> fails
        QDBusMessage reply = con.call(msg, QDBus::Block, 1);
        QCOMPARE(reply.type(), QDBusMessage::ErrorMessage);
    }

    // Now using QDBusInterface

    QDBusInterface iface(server_serviceName, server_objectPath, server_interfaceName, con);
    {
        // Call with no timeout
        QDBusMessage reply = iface.call("sleepMethod", 100);
        QCOMPARE(reply.type(), QDBusMessage::ReplyMessage);
        QCOMPARE(reply.arguments().at(0).toInt(), 42);
    }
    {
        // Call with 1 msec timeout -> fails
        iface.setTimeout(1);
        QDBusMessage reply = iface.call("sleepMethod", 100);
        QCOMPARE(reply.type(), QDBusMessage::ErrorMessage);
    }
    {
        // Call with 300 msec timeout -> works
        iface.setTimeout(300);
        QDBusMessage reply = iface.call("sleepMethod", 100);
        QCOMPARE(reply.arguments().at(0).toInt(), 42);
    }

    // Now using generated code
    org::qtproject::QtDBus::Pinger p(server_serviceName, server_objectPath, QDBusConnection::sessionBus());
    {
        // Call with no timeout
        QDBusReply<int> reply = p.sleepMethod(100);
        QVERIFY(reply.isValid());
        QCOMPARE(int(reply), 42);
    }
    {
        // Call with 1 msec timeout -> fails
        p.setTimeout(1);
        QDBusReply<int> reply = p.sleepMethod(100);
        QVERIFY(!reply.isValid());
    }
}

void tst_QDBusAbstractInterface::stringPropRead()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QString expectedValue = targetObj.m_stringProp = "This is a test";
    QVariant v = p->property("stringProp");
    QVERIFY(v.isValid());
    QCOMPARE(v.toString(), expectedValue);
}

void tst_QDBusAbstractInterface::stringPropWrite()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QString expectedValue = "This is a value";
    QVERIFY(p->setProperty("stringProp", expectedValue));
    QCOMPARE(targetObj.m_stringProp, expectedValue);
}

void tst_QDBusAbstractInterface::variantPropRead()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusVariant expectedValue = targetObj.m_variantProp = QDBusVariant(QVariant(42));
    QVariant v = p->property("variantProp");
    QVERIFY(v.isValid());
    QDBusVariant value = v.value<QDBusVariant>();
    QCOMPARE(value.variant().userType(), expectedValue.variant().userType());
    QCOMPARE(value.variant(), expectedValue.variant());
}

void tst_QDBusAbstractInterface::variantPropWrite()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusVariant expectedValue = QDBusVariant(Q_INT64_C(-47));
    QVERIFY(p->setProperty("variantProp", QVariant::fromValue(expectedValue)));
    QCOMPARE(targetObj.m_variantProp.variant(), expectedValue.variant());
}

void tst_QDBusAbstractInterface::complexPropRead()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    RegisteredType expectedValue = targetObj.m_complexProp = RegisteredType("This is a test");
    QVariant v = p->property("complexProp");
    QCOMPARE(v.userType(), qMetaTypeId<RegisteredType>());
    QCOMPARE(v.value<RegisteredType>(), targetObj.m_complexProp);
}

void tst_QDBusAbstractInterface::complexPropWrite()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    RegisteredType expectedValue = RegisteredType("This is a value");
    QVERIFY(p->setProperty("complexProp", QVariant::fromValue(expectedValue)));
    QCOMPARE(targetObj.m_complexProp, expectedValue);
}

void tst_QDBusAbstractInterface::stringPropReadPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    QString expectedValue = "This is a test";
    QVariant v = p->property("stringProp");
    QVERIFY(v.isValid());
    QCOMPARE(v.toString(), expectedValue);
}

void tst_QDBusAbstractInterface::stringPropWritePeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    QString expectedValue = "This is a value";
    QVERIFY(p->setProperty("stringProp", expectedValue));
    QEXPECT_FAIL("", "QTBUG-24262 peer tests are broken", Abort);
    QCOMPARE(targetObj.m_stringProp, expectedValue);
}

void tst_QDBusAbstractInterface::variantPropReadPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    QDBusVariant expectedValue = QDBusVariant(QVariant(42));
    QVariant v = p->property("variantProp");
    QVERIFY(v.isValid());
    QDBusVariant value = v.value<QDBusVariant>();
    QCOMPARE(value.variant().userType(), expectedValue.variant().userType());
    QCOMPARE(value.variant(), expectedValue.variant());
}

void tst_QDBusAbstractInterface::variantPropWritePeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    QDBusVariant expectedValue = QDBusVariant(Q_INT64_C(-47));
    QVERIFY(p->setProperty("variantProp", QVariant::fromValue(expectedValue)));
    QEXPECT_FAIL("", "QTBUG-24262 peer tests are broken", Abort);
    QCOMPARE(targetObj.m_variantProp.variant(), expectedValue.variant());
}

void tst_QDBusAbstractInterface::complexPropReadPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    RegisteredType expectedValue = RegisteredType("This is a test");
    QVariant v = p->property("complexProp");
    QCOMPARE(v.userType(), qMetaTypeId<RegisteredType>());
    QCOMPARE(v.value<RegisteredType>(), expectedValue);
}

void tst_QDBusAbstractInterface::complexPropWritePeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    RegisteredType expectedValue = RegisteredType("This is a value");
    QVERIFY(p->setProperty("complexProp", QVariant::fromValue(expectedValue)));
    QEXPECT_FAIL("", "QTBUG-24262 peer tests are broken", Abort);
    QCOMPARE(targetObj.m_complexProp, expectedValue);
}

void tst_QDBusAbstractInterface::stringPropDirectRead()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QString expectedValue = targetObj.m_stringProp = "This is a test";
    QCOMPARE(p->stringProp(), expectedValue);
}

void tst_QDBusAbstractInterface::stringPropDirectWrite()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QString expectedValue = "This is a value";
    p->setStringProp(expectedValue);
    QCOMPARE(targetObj.m_stringProp, expectedValue);
}

void tst_QDBusAbstractInterface::variantPropDirectRead()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusVariant expectedValue = targetObj.m_variantProp = QDBusVariant(42);
    QCOMPARE(p->variantProp().variant(), expectedValue.variant());
}

void tst_QDBusAbstractInterface::variantPropDirectWrite()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusVariant expectedValue = QDBusVariant(Q_INT64_C(-47));
    p->setVariantProp(expectedValue);
    QCOMPARE(targetObj.m_variantProp.variant().userType(), expectedValue.variant().userType());
    QCOMPARE(targetObj.m_variantProp.variant(), expectedValue.variant());
}

void tst_QDBusAbstractInterface::complexPropDirectRead()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    RegisteredType expectedValue = targetObj.m_complexProp = RegisteredType("This is a test");
    QCOMPARE(p->complexProp(), targetObj.m_complexProp);
}

void tst_QDBusAbstractInterface::complexPropDirectWrite()
{
    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    RegisteredType expectedValue = RegisteredType("This is a value");
    p->setComplexProp(expectedValue);
    QCOMPARE(targetObj.m_complexProp, expectedValue);
}

void tst_QDBusAbstractInterface::stringPropDirectReadPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    QString expectedValue = "This is a test";
    QCOMPARE(p->stringProp(), expectedValue);
}

void tst_QDBusAbstractInterface::stringPropDirectWritePeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    QString expectedValue = "This is a value";
    p->setStringProp(expectedValue);
    QEXPECT_FAIL("", "QTBUG-24262 peer tests are broken", Abort);
    QCOMPARE(targetObj.m_stringProp, expectedValue);
}

void tst_QDBusAbstractInterface::variantPropDirectReadPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    QDBusVariant expectedValue = QDBusVariant(42);
    QCOMPARE(p->variantProp().variant(), expectedValue.variant());
}

void tst_QDBusAbstractInterface::variantPropDirectWritePeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    QDBusVariant expectedValue = QDBusVariant(Q_INT64_C(-47));
    p->setVariantProp(expectedValue);
    QEXPECT_FAIL("", "QTBUG-24262 peer tests are broken", Abort);
    QCOMPARE(targetObj.m_variantProp.variant().userType(), expectedValue.variant().userType());
    QCOMPARE(targetObj.m_variantProp.variant(), expectedValue.variant());
}

void tst_QDBusAbstractInterface::complexPropDirectReadPeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    RegisteredType expectedValue = RegisteredType("This is a test");
    QCOMPARE(p->complexProp(), expectedValue);
}

void tst_QDBusAbstractInterface::complexPropDirectWritePeer()
{
    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");
    resetServer();

    RegisteredType expectedValue = RegisteredType("This is a value");
    p->setComplexProp(expectedValue);
    QEXPECT_FAIL("", "QTBUG-24262 peer tests are broken", Abort);
    QCOMPARE(targetObj.m_complexProp, expectedValue);
}

void tst_QDBusAbstractInterface::getVoidSignal_data()
{
    QTest::addColumn<QString>("service");
    QTest::addColumn<QString>("path");

    QTest::newRow("specific") << QDBusConnection::sessionBus().baseService() << "/";
    QTest::newRow("service-wildcard") << QString() << "/";
    QTest::newRow("path-wildcard") << QDBusConnection::sessionBus().baseService() << QString();
    QTest::newRow("full-wildcard") << QString() << QString();
}

void tst_QDBusAbstractInterface::getVoidSignal()
{
    QFETCH(QString, service);
    QFETCH(QString, path);
    Pinger p = getPinger(service, path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we need to connect the signal somewhere in order for D-Bus to enable the rules
    QTestEventLoop::instance().connect(p.data(), SIGNAL(voidSignal()), SLOT(exitLoop()));
    QSignalSpy s(p.data(), SIGNAL(voidSignal()));

    emit targetObj.voidSignal();
    QTestEventLoop::instance().enterLoop(2);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(s.size(), 1);
    QCOMPARE(s.at(0).size(), 0);
}

void tst_QDBusAbstractInterface::getStringSignal_data()
{
    getVoidSignal_data();
}

void tst_QDBusAbstractInterface::getStringSignal()
{
    QFETCH(QString, service);
    QFETCH(QString, path);
    Pinger p = getPinger(service, path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we need to connect the signal somewhere in order for D-Bus to enable the rules
    QTestEventLoop::instance().connect(p.data(), SIGNAL(stringSignal(QString)), SLOT(exitLoop()));
    QSignalSpy s(p.data(), SIGNAL(stringSignal(QString)));

    QString expectedValue = "Good morning";
    emit targetObj.stringSignal(expectedValue);
    QTestEventLoop::instance().enterLoop(2);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(s.size(), 1);
    QCOMPARE(s[0].size(), 1);
    QCOMPARE(s[0][0].userType(), int(QVariant::String));
    QCOMPARE(s[0][0].toString(), expectedValue);
}

void tst_QDBusAbstractInterface::getComplexSignal_data()
{
    getVoidSignal_data();
}

void tst_QDBusAbstractInterface::getComplexSignal()
{
    QFETCH(QString, service);
    QFETCH(QString, path);
    Pinger p = getPinger(service, path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we need to connect the signal somewhere in order for D-Bus to enable the rules
    QTestEventLoop::instance().connect(p.data(), SIGNAL(complexSignal(RegisteredType)), SLOT(exitLoop()));
    QSignalSpy s(p.data(), SIGNAL(complexSignal(RegisteredType)));

    RegisteredType expectedValue("Good evening");
    emit targetObj.complexSignal(expectedValue);
    QTestEventLoop::instance().enterLoop(2);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(s.size(), 1);
    QCOMPARE(s[0].size(), 1);
    QCOMPARE(s[0][0].userType(), qMetaTypeId<RegisteredType>());
    QCOMPARE(s[0][0].value<RegisteredType>(), expectedValue);
}

void tst_QDBusAbstractInterface::getVoidSignalPeer_data()
{
    QTest::addColumn<QString>("path");

    QTest::newRow("specific") << "/";
    QTest::newRow("wildcard") << QString();
}

void tst_QDBusAbstractInterface::getVoidSignalPeer()
{
    QFETCH(QString, path);
    Pinger p = getPingerPeer(path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we need to connect the signal somewhere in order for D-Bus to enable the rules
    QTestEventLoop::instance().connect(p.data(), SIGNAL(voidSignal()), SLOT(exitLoop()));
    QSignalSpy s(p.data(), SIGNAL(voidSignal()));

    QDBusMessage req = QDBusMessage::createMethodCall(serviceName, objectPath, interfaceName, "voidSignal");
    QVERIFY(QDBusConnection::sessionBus().send(req));
    QTestEventLoop::instance().enterLoop(2);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(s.size(), 1);
    QCOMPARE(s.at(0).size(), 0);
}

void tst_QDBusAbstractInterface::getStringSignalPeer_data()
{
    getVoidSignalPeer_data();
}

void tst_QDBusAbstractInterface::getStringSignalPeer()
{
    QFETCH(QString, path);
    Pinger p = getPingerPeer(path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we need to connect the signal somewhere in order for D-Bus to enable the rules
    QTestEventLoop::instance().connect(p.data(), SIGNAL(stringSignal(QString)), SLOT(exitLoop()));
    QSignalSpy s(p.data(), SIGNAL(stringSignal(QString)));

    QString expectedValue = "Good morning";
    QDBusMessage req = QDBusMessage::createMethodCall(serviceName, objectPath, interfaceName, "stringSignal");
    req << expectedValue;
    QVERIFY(QDBusConnection::sessionBus().send(req));
    QTestEventLoop::instance().enterLoop(2);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(s.size(), 1);
    QCOMPARE(s[0].size(), 1);
    QCOMPARE(s[0][0].userType(), int(QVariant::String));
    QCOMPARE(s[0][0].toString(), expectedValue);
}

void tst_QDBusAbstractInterface::getComplexSignalPeer_data()
{
    getVoidSignalPeer_data();
}

void tst_QDBusAbstractInterface::getComplexSignalPeer()
{
    QFETCH(QString, path);
    Pinger p = getPingerPeer(path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we need to connect the signal somewhere in order for D-Bus to enable the rules
    QTestEventLoop::instance().connect(p.data(), SIGNAL(complexSignal(RegisteredType)), SLOT(exitLoop()));
    QSignalSpy s(p.data(), SIGNAL(complexSignal(RegisteredType)));

    RegisteredType expectedValue("Good evening");
    QDBusMessage req = QDBusMessage::createMethodCall(serviceName, objectPath, interfaceName, "complexSignal");
    req << "Good evening";
    QVERIFY(QDBusConnection::sessionBus().send(req));
    QTestEventLoop::instance().enterLoop(2);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(s.size(), 1);
    QCOMPARE(s[0].size(), 1);
    QCOMPARE(s[0][0].userType(), qMetaTypeId<RegisteredType>());
    QCOMPARE(s[0][0].value<RegisteredType>(), expectedValue);
}

void tst_QDBusAbstractInterface::followSignal()
{
    const QString serviceToFollow = "org.qtproject.tst_qdbusabstractinterface.FollowMe";
    Pinger p = getPinger(serviceToFollow);
    QVERIFY2(p, "Not connected to D-Bus");

    QDBusConnection con = p->connection();
    QVERIFY(!con.interface()->isServiceRegistered(serviceToFollow));
    Pinger control = getPinger("");

    // connect our test signal
    // FRAGILE CODE AHEAD:
    // Connection order is important: we connect the control first because that
    // needs to be delivered last, to ensure that we don't exitLoop() before
    // the signal delivery to QSignalSpy is posted to the current thread. That
    // happens because QDBusConnectionPrivate runs in a separate thread and
    // uses a QMultiHash and insertMulti prepends to the list of items with the
    // same key.
    QTestEventLoop::instance().connect(control.data(), SIGNAL(voidSignal()), SLOT(exitLoop()));
    QSignalSpy s(p.data(), SIGNAL(voidSignal()));

    emit targetObj.voidSignal();
    QTestEventLoop::instance().enterLoop(200);
    QVERIFY(!QTestEventLoop::instance().timeout());

    // signal must not have been received because the service isn't registered
    QVERIFY(s.isEmpty());

    // now register the service
    QDBusReply<QDBusConnectionInterface::RegisterServiceReply> r =
            con.interface()->registerService(serviceToFollow, QDBusConnectionInterface::DontQueueService,
                                             QDBusConnectionInterface::DontAllowReplacement);
    QVERIFY(r.isValid() && r.value() == QDBusConnectionInterface::ServiceRegistered);
    QVERIFY(con.interface()->isServiceRegistered(serviceToFollow));
    QCoreApplication::instance()->processEvents();

    // emit the signal again:
    emit targetObj.voidSignal();
    QTestEventLoop::instance().enterLoop(2);
    QVERIFY(!QTestEventLoop::instance().timeout());

    // now the signal must have been received:
    QCOMPARE(s.size(), 1);
    QCOMPARE(s.at(0).size(), 0);

    // cleanup:
    con.interface()->unregisterService(serviceToFollow);
}

void tst_QDBusAbstractInterface::connectDisconnect_data()
{
    QTest::addColumn<int>("connectCount");
    QTest::addColumn<int>("disconnectCount");

    // we don't actually need multiple disconnects
    // QObject::disconnect() disconnects all matching rules
    // we'd have to use QMetaObject::disconnectOne if we wanted just one
    QTest::newRow("null") << 0 << 0;
    QTest::newRow("connect-disconnect") << 1 << 1;
    QTest::newRow("connect-disconnect-wildcard") << 1 << -1;
    QTest::newRow("connect-twice") << 2 << 0;
    QTest::newRow("connect-twice-disconnect") << 2 << 1;
    QTest::newRow("connect-twice-disconnect-wildcard") << 2 << -1;
}

void tst_QDBusAbstractInterface::connectDisconnect()
{
    QFETCH(int, connectCount);
    QFETCH(int, disconnectCount);

    Pinger p = getPinger();
    QVERIFY2(p, "Not connected to D-Bus");

    // connect the exitLoop slot first
    // if the disconnect() below does something weird, we'll get a timeout
    QTestEventLoop::instance().connect(p.data(), SIGNAL(voidSignal()), SLOT(exitLoop()));

    SignalReceiver sr;
    for (int i = 0; i < connectCount; ++i)
        sr.connect(p.data(), SIGNAL(voidSignal()), SLOT(receive()));
    if (disconnectCount)
        QObject::disconnect(p.data(), disconnectCount > 0 ? SIGNAL(voidSignal()) : 0, &sr, SLOT(receive()));

    emit targetObj.voidSignal();
    QTestEventLoop::instance().enterLoop(2);
    QVERIFY(!QTestEventLoop::instance().timeout());

    if (disconnectCount != 0)
        QCOMPARE(sr.callCount, 0);
    else
        QCOMPARE(sr.callCount, connectCount);
}

void tst_QDBusAbstractInterface::connectDisconnectPeer_data()
{
    connectDisconnect_data();
}

void tst_QDBusAbstractInterface::connectDisconnectPeer()
{
    QFETCH(int, connectCount);
    QFETCH(int, disconnectCount);

    Pinger p = getPingerPeer();
    QVERIFY2(p, "Not connected to D-Bus");

    // connect the exitLoop slot first
    // if the disconnect() below does something weird, we'll get a timeout
    QTestEventLoop::instance().connect(p.data(), SIGNAL(voidSignal()), SLOT(exitLoop()));

    SignalReceiver sr;
    for (int i = 0; i < connectCount; ++i)
        sr.connect(p.data(), SIGNAL(voidSignal()), SLOT(receive()));
    if (disconnectCount)
        QObject::disconnect(p.data(), disconnectCount > 0 ? SIGNAL(voidSignal()) : 0, &sr, SLOT(receive()));

    QDBusMessage req = QDBusMessage::createMethodCall(serviceName, objectPath, interfaceName, "voidSignal");
    QVERIFY(QDBusConnection::sessionBus().send(req));
    QTestEventLoop::instance().enterLoop(2);
    QVERIFY(!QTestEventLoop::instance().timeout());

    if (disconnectCount != 0)
        QCOMPARE(sr.callCount, 0);
    else
        QCOMPARE(sr.callCount, connectCount);
}

void tst_QDBusAbstractInterface::createErrors_data()
{
    QTest::addColumn<QString>("service");
    QTest::addColumn<QString>("path");
    QTest::addColumn<QString>("errorName");

    QTest::newRow("invalid-service") << "this isn't valid" << "/" << "org.qtproject.QtDBus.Error.InvalidService";
    QTest::newRow("invalid-path") << QDBusConnection::sessionBus().baseService() << "this isn't valid"
            << "org.qtproject.QtDBus.Error.InvalidObjectPath";
}

void tst_QDBusAbstractInterface::createErrors()
{
    QFETCH(QString, service);
    QFETCH(QString, path);
    Pinger p = getPinger(service, path);
    QVERIFY2(p, "Not connected to D-Bus");

    QVERIFY(!p->isValid());
    QTEST(p->lastError().name(), "errorName");
}

void tst_QDBusAbstractInterface::createErrorsPeer_data()
{
    QTest::addColumn<QString>("path");
    QTest::addColumn<QString>("errorName");

    QTest::newRow("invalid-path") << "this isn't valid" << "org.qtproject.QtDBus.Error.InvalidObjectPath";
}

void tst_QDBusAbstractInterface::createErrorsPeer()
{
    QFETCH(QString, path);
    Pinger p = getPingerPeer(path);
    QVERIFY2(p, "Not connected to D-Bus");

    QVERIFY(!p->isValid());
    QTEST(p->lastError().name(), "errorName");
}

void tst_QDBusAbstractInterface::callErrors_data()
{
    createErrors_data();
    QTest::newRow("service-wildcard") << QString() << "/" << "org.qtproject.QtDBus.Error.InvalidService";
    QTest::newRow("path-wildcard") << QDBusConnection::sessionBus().baseService() << QString()
            << "org.qtproject.QtDBus.Error.InvalidObjectPath";
    QTest::newRow("full-wildcard") << QString() << QString() << "org.qtproject.QtDBus.Error.InvalidService";
}

void tst_QDBusAbstractInterface::callErrors()
{
    QFETCH(QString, service);
    QFETCH(QString, path);
    Pinger p = getPinger(service, path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to make this call:
    QDBusReply<QString> r = p->stringMethod();
    QVERIFY(!r.isValid());
    QTEST(r.error().name(), "errorName");
    QCOMPARE(p->lastError().name(), r.error().name());
}

void tst_QDBusAbstractInterface::asyncCallErrors_data()
{
    callErrors_data();
}

void tst_QDBusAbstractInterface::asyncCallErrors()
{
    QFETCH(QString, service);
    QFETCH(QString, path);
    Pinger p = getPinger(service, path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to make this call:
    QDBusPendingReply<QString> r = p->stringMethod();
    QVERIFY(r.isError());
    QTEST(r.error().name(), "errorName");
    QCOMPARE(p->lastError().name(), r.error().name());
}

void tst_QDBusAbstractInterface::callErrorsPeer_data()
{
    createErrorsPeer_data();
    QTest::newRow("path-wildcard") << QString() << "org.qtproject.QtDBus.Error.InvalidObjectPath";
}

void tst_QDBusAbstractInterface::callErrorsPeer()
{
    QFETCH(QString, path);
    Pinger p = getPingerPeer(path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to make this call:
    QDBusReply<QString> r = p->stringMethod();
    QVERIFY(!r.isValid());
    QTEST(r.error().name(), "errorName");
    QCOMPARE(p->lastError().name(), r.error().name());
}

void tst_QDBusAbstractInterface::asyncCallErrorsPeer_data()
{
    callErrorsPeer_data();
}

void tst_QDBusAbstractInterface::asyncCallErrorsPeer()
{
    QFETCH(QString, path);
    Pinger p = getPingerPeer(path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to make this call:
    QDBusPendingReply<QString> r = p->stringMethod();
    QVERIFY(r.isError());
    QTEST(r.error().name(), "errorName");
    QCOMPARE(p->lastError().name(), r.error().name());
}

void tst_QDBusAbstractInterface::propertyReadErrors_data()
{
    callErrors_data();
}

void tst_QDBusAbstractInterface::propertyReadErrors()
{
    QFETCH(QString, service);
    QFETCH(QString, path);
    Pinger p = getPinger(service, path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to get this value:
    QVariant v = p->property("stringProp");
    QVERIFY(v.isNull());
    QVERIFY(!v.isValid());
    QTEST(p->lastError().name(), "errorName");
}

void tst_QDBusAbstractInterface::propertyWriteErrors_data()
{
    callErrors_data();
}

void tst_QDBusAbstractInterface::propertyWriteErrors()
{
    QFETCH(QString, service);
    QFETCH(QString, path);
    Pinger p = getPinger(service, path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to get this value:
    if (p->isValid())
        QCOMPARE(int(p->lastError().type()), int(QDBusError::NoError));
    QVERIFY(!p->setProperty("stringProp", ""));
    QTEST(p->lastError().name(), "errorName");
}

void tst_QDBusAbstractInterface::directPropertyReadErrors_data()
{
    callErrors_data();
}

void tst_QDBusAbstractInterface::directPropertyReadErrors()
{
    QFETCH(QString, service);
    QFETCH(QString, path);
    Pinger p = getPinger(service, path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to get this value:
    QString v = p->stringProp();
    QVERIFY(v.isNull());
    QTEST(p->lastError().name(), "errorName");
}

void tst_QDBusAbstractInterface::directPropertyWriteErrors_data()
{
    callErrors_data();
}

void tst_QDBusAbstractInterface::directPropertyWriteErrors()
{
    QFETCH(QString, service);
    QFETCH(QString, path);
    Pinger p = getPinger(service, path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to get this value:
    // but there's no direct way of verifying that the setting failed
    if (p->isValid())
        QCOMPARE(int(p->lastError().type()), int(QDBusError::NoError));
    p->setStringProp("");
    QTEST(p->lastError().name(), "errorName");
}

void tst_QDBusAbstractInterface::propertyReadErrorsPeer_data()
{
    callErrorsPeer_data();
}

void tst_QDBusAbstractInterface::propertyReadErrorsPeer()
{
    QFETCH(QString, path);
    Pinger p = getPingerPeer(path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to get this value:
    QVariant v = p->property("stringProp");
    QVERIFY(v.isNull());
    QVERIFY(!v.isValid());
    QTEST(p->lastError().name(), "errorName");
}

void tst_QDBusAbstractInterface::propertyWriteErrorsPeer_data()
{
    callErrorsPeer_data();
}

void tst_QDBusAbstractInterface::propertyWriteErrorsPeer()
{
    QFETCH(QString, path);
    Pinger p = getPingerPeer(path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to get this value:
    if (p->isValid())
        QCOMPARE(int(p->lastError().type()), int(QDBusError::NoError));
    QVERIFY(!p->setProperty("stringProp", ""));
    QTEST(p->lastError().name(), "errorName");
}

void tst_QDBusAbstractInterface::directPropertyReadErrorsPeer_data()
{
    callErrorsPeer_data();
}

void tst_QDBusAbstractInterface::directPropertyReadErrorsPeer()
{
    QFETCH(QString, path);
    Pinger p = getPingerPeer(path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to get this value:
    QString v = p->stringProp();
    QVERIFY(v.isNull());
    QTEST(p->lastError().name(), "errorName");
}

void tst_QDBusAbstractInterface::directPropertyWriteErrorsPeer_data()
{
    callErrorsPeer_data();
}

void tst_QDBusAbstractInterface::directPropertyWriteErrorsPeer()
{
    QFETCH(QString, path);
    Pinger p = getPingerPeer(path);
    QVERIFY2(p, "Not connected to D-Bus");

    // we shouldn't be able to get this value:
    // but there's no direct way of verifying that the setting failed
    if (p->isValid())
        QCOMPARE(int(p->lastError().type()), int(QDBusError::NoError));
    p->setStringProp("");
    QTEST(p->lastError().name(), "errorName");
}

void tst_QDBusAbstractInterface::validity_data()
{
    QTest::addColumn<QString>("service");

    QTest::newRow("null-service") << "";
    QTest::newRow("ignored-service") << "org.example.anyservice";
}

void tst_QDBusAbstractInterface::validity()
{
    /* Test case for QTBUG-32374 */
    QFETCH(QString, service);
    Pinger p = getPingerPeer("/", service);
    QVERIFY2(p, "Not connected to D-Bus");

    QVERIFY(p->isValid());
}

QTEST_MAIN(tst_QDBusAbstractInterface)
#include "tst_qdbusabstractinterface.moc"
